mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
refactor(zone): remove legacy route domain storage
第二阶段清理:删除 of_managed_domains 与 of_proxy_routes 冗余域名/证书列, 移除 ManagedDomain 模型与 API、路由侧 legacy 字段维护,以及前端 WebsiteService。 ImportLegacy 在旧列/旧表缺失时跳过对应源,保持幂等。
This commit is contained in:
@@ -14404,32 +14404,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.ManagedDomain": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cert_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"remark": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.OpenFlareApplyLog": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -17959,63 +17933,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls.ManagedDomainInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cert_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remark": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls.ManagedDomainMatchCandidate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"certificate_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"certificate_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"managed_domain_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"match_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls.ManagedDomainMatchResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"candidate": {
|
||||
"$ref": "#/definitions/tls.ManagedDomainMatchCandidate"
|
||||
},
|
||||
"candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tls.ManagedDomainMatchCandidate"
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"matched": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updater.Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -416,27 +416,27 @@ git commit -m "refactor(web): select route domains from zones"
|
||||
- Modify: generated `docs/{docs.go,swagger.json,swagger.yaml}`
|
||||
- Create: `docs/guide/zone-domain-migration.md`
|
||||
|
||||
- [ ] **Step 1: 为导入命令写可操作迁移指南**
|
||||
- [x] **Step 1: 为导入命令写可操作迁移指南**
|
||||
|
||||
文档写明备份、执行 `wavelet migrate-zones`、读取导入报告、发布预览、比较 `server_name`/证书支持文件、发布激活和回滚步骤;不允许在报告有冲突时继续。
|
||||
|
||||
- [ ] **Step 2: 生成 Swagger 和更新未发布变更**
|
||||
- [x] **Step 2: 生成 Swagger 和更新未发布变更**
|
||||
|
||||
Run: `make swagger`
|
||||
|
||||
在 `[Unreleased]` 记录 Zone 管理、反代路由域名正规化和移除 managed-domain API。
|
||||
|
||||
- [ ] **Step 3: 运行全量质量门禁**
|
||||
- [x] **Step 3: 运行全量质量门禁**
|
||||
|
||||
Run: `go test ./... && make code-check`
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 4: 做快照等价性验收**
|
||||
- [x] **Step 4: 做快照等价性验收**
|
||||
|
||||
在升级前导出活动版本,在导入后生成预览;逐个比较所有路由的明确 `server_name` 集合、证书路径、WAF RouteID 绑定与 Pages 部署引用。只允许旧快照的域名/证书冗余 JSON 消失,不允许数据面语义变化。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [x] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs
|
||||
@@ -455,7 +455,7 @@ git commit -m "docs(zone): add migration and release verification guide"
|
||||
- Delete: `internal/apps/openflare/tls/helpers.go` 中仅用于旧路由证书数组的函数
|
||||
- Modify: legacy迁移相关测试、模型测试与 `docs/design/zone-design.md`
|
||||
|
||||
- [ ] **Step 1: 写空库与升级库清理失败测试**
|
||||
- [x] **Step 1: 写空库与升级库清理失败测试**
|
||||
|
||||
```go
|
||||
func TestLegacyRouteColumnsAreAbsentAfterCleanup(t *testing.T) {
|
||||
@@ -463,21 +463,21 @@ func TestLegacyRouteColumnsAreAbsentAfterCleanup(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 编写双方言清理 DDL**
|
||||
- [x] **Step 2: 编写双方言清理 DDL**
|
||||
|
||||
PostgreSQL 删除旧唯一索引和 `domain`、`domains`、`cert_id`、`cert_ids`、`domain_cert_ids`,再删除 `of_managed_domains`;SQLite 使用重建 `of_proxy_routes` 表的迁移方式保留所有非旧字段与索引。Down 仅在开发数据库恢复旧结构,不回填历史数据。
|
||||
|
||||
- [ ] **Step 3: 删除旧读取代码与测试 fixture**
|
||||
- [x] **Step 3: 删除旧读取代码与测试 fixture**
|
||||
|
||||
删除所有 `route.Domain`、`route.Domains`、`route.CertID`、`route.CertIDs`、`route.DomainCertIDs` 的持久化引用;让编译器、Uptime Kuma、Flared、来源摘要及 API 只使用 ZoneDomain 查询结果。
|
||||
|
||||
- [ ] **Step 4: 验证升级和完整回归**
|
||||
- [x] **Step 4: 验证升级和完整回归**
|
||||
|
||||
Run: `go test ./internal/db/migrator ./internal/model ./internal/apps/openflare/... ./pkg/render/openresty -count=1 && make code-check`
|
||||
|
||||
Expected: PASS;全仓搜索不再发现旧 `ManagedDomain` 业务代码、`ProxyRoute` 持久化字段或管理端 API;渲染快照中的临时 `DomainCertIDs` 类型允许保留。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [x] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/db/migrator internal/model internal/apps frontend docs
|
||||
|
||||
@@ -14397,32 +14397,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.ManagedDomain": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cert_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"remark": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.OpenFlareApplyLog": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -17952,63 +17926,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls.ManagedDomainInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cert_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remark": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls.ManagedDomainMatchCandidate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"certificate_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"certificate_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"managed_domain_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"match_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls.ManagedDomainMatchResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"candidate": {
|
||||
"$ref": "#/definitions/tls.ManagedDomainMatchCandidate"
|
||||
},
|
||||
"candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tls.ManagedDomainMatchCandidate"
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"matched": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updater.Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1104,23 +1104,6 @@ definitions:
|
||||
id:
|
||||
type: integer
|
||||
type: object
|
||||
model.ManagedDomain:
|
||||
properties:
|
||||
cert_id:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
domain:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
id:
|
||||
type: integer
|
||||
remark:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
model.OpenFlareApplyLog:
|
||||
properties:
|
||||
checksum:
|
||||
@@ -3471,43 +3454,6 @@ definitions:
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
tls.ManagedDomainInput:
|
||||
properties:
|
||||
cert_id:
|
||||
type: integer
|
||||
domain:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
remark:
|
||||
type: string
|
||||
type: object
|
||||
tls.ManagedDomainMatchCandidate:
|
||||
properties:
|
||||
certificate_id:
|
||||
type: integer
|
||||
certificate_name:
|
||||
type: string
|
||||
domain:
|
||||
type: string
|
||||
managed_domain_id:
|
||||
type: integer
|
||||
match_type:
|
||||
type: string
|
||||
type: object
|
||||
tls.ManagedDomainMatchResult:
|
||||
properties:
|
||||
candidate:
|
||||
$ref: '#/definitions/tls.ManagedDomainMatchCandidate'
|
||||
candidates:
|
||||
items:
|
||||
$ref: '#/definitions/tls.ManagedDomainMatchCandidate'
|
||||
type: array
|
||||
domain:
|
||||
type: string
|
||||
matched:
|
||||
type: boolean
|
||||
type: object
|
||||
updater.Status:
|
||||
properties:
|
||||
asset_name:
|
||||
|
||||
@@ -62,8 +62,7 @@ export function CertificateEditorDialog({
|
||||
onSuccess: async (certificate) => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({queryKey: certificatesQueryKey}),
|
||||
queryClient.invalidateQueries({queryKey: ['openflare', 'managed-domains']}),
|
||||
]);
|
||||
]);
|
||||
onSaved?.(certificate);
|
||||
handleClose();
|
||||
},
|
||||
|
||||
@@ -58,8 +58,7 @@ export function CertificateImportDialog({
|
||||
const invalidateQueries = async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({queryKey: certificatesQueryKey}),
|
||||
queryClient.invalidateQueries({queryKey: ['openflare', 'managed-domains']}),
|
||||
]);
|
||||
]);
|
||||
};
|
||||
|
||||
const resetFileForm = () => {
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
TlsCertificateService,
|
||||
UptimeKumaService,
|
||||
WafService,
|
||||
WebsiteService,
|
||||
ZoneDomainService,
|
||||
ZoneService,
|
||||
} from './openflare';
|
||||
@@ -68,7 +67,6 @@ const services = {
|
||||
openflareApplyLog: ApplyLogService,
|
||||
openflareDashboard: DashboardService,
|
||||
openflareWaf: WafService,
|
||||
openflareWebsite: WebsiteService,
|
||||
openflareZone: ZoneService,
|
||||
openflareZoneDomain: ZoneDomainService,
|
||||
openflareTls: TlsCertificateService,
|
||||
@@ -191,7 +189,6 @@ export {
|
||||
ApplyLogService,
|
||||
DashboardService,
|
||||
WafService,
|
||||
WebsiteService,
|
||||
TlsCertificateService,
|
||||
DnsAccountService,
|
||||
PagesService,
|
||||
@@ -216,7 +213,6 @@ export type {
|
||||
WAFIPGroup,
|
||||
WAFRuleGroup,
|
||||
WAFSiteRuleGroups,
|
||||
ManagedDomainItem,
|
||||
TlsCertificateItem,
|
||||
DnsAccountItem,
|
||||
PagesProject,
|
||||
|
||||
@@ -6,7 +6,6 @@ export { ConfigVersionService } from './config-version.service';
|
||||
export { ApplyLogService } from './apply-log.service';
|
||||
export { DashboardService } from './dashboard.service';
|
||||
export { WafService } from './waf.service';
|
||||
export { WebsiteService } from './website.service';
|
||||
export { ZoneDomainService, ZoneService, zoneQueryKey } from './zone.service';
|
||||
export { TlsCertificateService } from './tls-certificate.service';
|
||||
export { DnsAccountService } from './dns-account.service';
|
||||
@@ -104,9 +103,6 @@ export type {
|
||||
AcmeAccountItem,
|
||||
DnsAccountItem,
|
||||
DnsAccountMutationPayload,
|
||||
ManagedDomainItem,
|
||||
ManagedDomainMatchResult,
|
||||
ManagedDomainMutationPayload,
|
||||
ZoneDomainItem,
|
||||
ZoneDomainMutationPayload,
|
||||
ZoneItem,
|
||||
@@ -134,7 +130,7 @@ import {PagesService} from './pages.service';
|
||||
import {ProxyRouteService} from './proxy-route.service';
|
||||
import {TlsCertificateService} from './tls-certificate.service';
|
||||
import {WafService} from './waf.service';
|
||||
import {WebsiteService} from './website.service';
|
||||
import {ZoneDomainService, ZoneService} from './zone.service';
|
||||
|
||||
export const openflareServices = {
|
||||
node: NodeService,
|
||||
@@ -143,7 +139,8 @@ export const openflareServices = {
|
||||
applyLog: ApplyLogService,
|
||||
dashboard: DashboardService,
|
||||
waf: WafService,
|
||||
website: WebsiteService,
|
||||
zone: ZoneService,
|
||||
zoneDomain: ZoneDomainService,
|
||||
tlsCertificate: TlsCertificateService,
|
||||
dnsAccount: DnsAccountService,
|
||||
pages: PagesService,
|
||||
|
||||
@@ -998,37 +998,9 @@ export interface ZoneOverview {
|
||||
domains: ZoneDomainItem[];
|
||||
}
|
||||
|
||||
export interface ManagedDomainItem {
|
||||
id: number;
|
||||
domain: string;
|
||||
cert_id: number | null;
|
||||
enabled: boolean;
|
||||
remark: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ManagedDomainMutationPayload {
|
||||
domain: string;
|
||||
cert_id: number | null;
|
||||
enabled: boolean;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
export interface ManagedDomainMatchCandidate {
|
||||
managed_domain_id: number;
|
||||
domain: string;
|
||||
match_type: 'exact' | 'wildcard' | string;
|
||||
certificate_id: number;
|
||||
certificate_name: string;
|
||||
}
|
||||
|
||||
export interface ManagedDomainMatchResult {
|
||||
domain: string;
|
||||
matched: boolean;
|
||||
candidate?: ManagedDomainMatchCandidate;
|
||||
candidates: ManagedDomainMatchCandidate[];
|
||||
}
|
||||
|
||||
export interface TlsCertificateItem {
|
||||
id: number;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import {OpenFlareBaseService} from './base.service';
|
||||
import type {
|
||||
ManagedDomainItem,
|
||||
ManagedDomainMatchResult,
|
||||
ManagedDomainMutationPayload,
|
||||
} from './types';
|
||||
|
||||
export class WebsiteService extends OpenFlareBaseService {
|
||||
protected static override readonly basePath: string = '/api/v1/d/managed-domains';
|
||||
|
||||
static async list(): Promise<ManagedDomainItem[]> {
|
||||
return this.get<ManagedDomainItem[]>('/');
|
||||
}
|
||||
|
||||
static async create(
|
||||
payload: ManagedDomainMutationPayload,
|
||||
): Promise<ManagedDomainItem> {
|
||||
return this.post<ManagedDomainItem>('/', payload);
|
||||
}
|
||||
|
||||
static async update(
|
||||
id: number,
|
||||
payload: ManagedDomainMutationPayload,
|
||||
): Promise<ManagedDomainItem> {
|
||||
return this.post<ManagedDomainItem>(`/${id}/update`, payload);
|
||||
}
|
||||
|
||||
static async deleteById(id: number): Promise<void> {
|
||||
return this.post<void>(`/${id}/delete`);
|
||||
}
|
||||
|
||||
static async match(domain: string): Promise<ManagedDomainMatchResult> {
|
||||
return this.get<ManagedDomainMatchResult>('/match', {domain});
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
-- +goose Up
|
||||
-- 第二阶段:删除反代路由冗余域名/证书列与托管域名表。
|
||||
-- 执行前必须完成 migrate-zones 且配置预览验收通过。
|
||||
|
||||
DROP INDEX IF EXISTS idx_of_proxy_routes_domain;
|
||||
|
||||
ALTER TABLE of_proxy_routes DROP COLUMN IF EXISTS domain;
|
||||
ALTER TABLE of_proxy_routes DROP COLUMN IF EXISTS domains;
|
||||
ALTER TABLE of_proxy_routes DROP COLUMN IF EXISTS cert_id;
|
||||
ALTER TABLE of_proxy_routes DROP COLUMN IF EXISTS cert_ids;
|
||||
ALTER TABLE of_proxy_routes DROP COLUMN IF EXISTS domain_cert_ids;
|
||||
|
||||
DROP TABLE IF EXISTS of_managed_domains;
|
||||
|
||||
-- +goose Down
|
||||
-- 仅恢复开发库结构,不回填历史域名/证书数据。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS of_managed_domains (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
domain VARCHAR(255) NOT NULL,
|
||||
cert_id BIGINT,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_managed_domains_domain ON of_managed_domains (domain);
|
||||
|
||||
ALTER TABLE of_proxy_routes ADD COLUMN IF NOT EXISTS domain VARCHAR(255) NOT NULL DEFAULT '';
|
||||
ALTER TABLE of_proxy_routes ADD COLUMN IF NOT EXISTS domains TEXT NOT NULL DEFAULT '[]';
|
||||
ALTER TABLE of_proxy_routes ADD COLUMN IF NOT EXISTS cert_id BIGINT;
|
||||
ALTER TABLE of_proxy_routes ADD COLUMN IF NOT EXISTS cert_ids TEXT NOT NULL DEFAULT '[]';
|
||||
ALTER TABLE of_proxy_routes ADD COLUMN IF NOT EXISTS domain_cert_ids TEXT NOT NULL DEFAULT '[]';
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_proxy_routes_domain ON of_proxy_routes (domain);
|
||||
@@ -0,0 +1,138 @@
|
||||
-- +goose Up
|
||||
-- 第二阶段:重建 of_proxy_routes(去掉冗余域名/证书列)并删除 of_managed_domains。
|
||||
|
||||
PRAGMA foreign_keys=OFF;
|
||||
|
||||
CREATE TABLE of_proxy_routes_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_name TEXT NOT NULL DEFAULT '',
|
||||
origin_id INTEGER,
|
||||
origin_url TEXT NOT NULL,
|
||||
origin_host TEXT NOT NULL DEFAULT '',
|
||||
upstreams TEXT NOT NULL DEFAULT '[]',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
enable_https INTEGER NOT NULL DEFAULT 0,
|
||||
redirect_http INTEGER NOT NULL DEFAULT 0,
|
||||
limit_conn_per_server INTEGER NOT NULL DEFAULT 0,
|
||||
limit_conn_per_ip INTEGER NOT NULL DEFAULT 0,
|
||||
limit_rate TEXT NOT NULL DEFAULT '',
|
||||
cache_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
cache_policy TEXT NOT NULL DEFAULT '',
|
||||
cache_rules TEXT NOT NULL DEFAULT '[]',
|
||||
custom_headers TEXT NOT NULL DEFAULT '[]',
|
||||
basic_auth_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
basic_auth_username TEXT NOT NULL DEFAULT '',
|
||||
basic_auth_password TEXT NOT NULL DEFAULT '',
|
||||
remark TEXT NOT NULL DEFAULT '',
|
||||
upstream_type TEXT NOT NULL DEFAULT 'direct',
|
||||
tunnel_node_id INTEGER,
|
||||
tunnel_target_addr TEXT NOT NULL DEFAULT '',
|
||||
tunnel_target_protocol TEXT NOT NULL DEFAULT '',
|
||||
pages_project_id INTEGER,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO of_proxy_routes_new (
|
||||
id, site_name, origin_id, origin_url, origin_host, upstreams, enabled,
|
||||
enable_https, redirect_http, limit_conn_per_server, limit_conn_per_ip, limit_rate,
|
||||
cache_enabled, cache_policy, cache_rules, custom_headers, basic_auth_enabled,
|
||||
basic_auth_username, basic_auth_password, remark, upstream_type, tunnel_node_id,
|
||||
tunnel_target_addr, tunnel_target_protocol, pages_project_id, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id, site_name, origin_id, origin_url, origin_host, upstreams, enabled,
|
||||
enable_https, redirect_http, limit_conn_per_server, limit_conn_per_ip, limit_rate,
|
||||
cache_enabled, cache_policy, cache_rules, custom_headers, basic_auth_enabled,
|
||||
basic_auth_username, basic_auth_password, remark, upstream_type, tunnel_node_id,
|
||||
tunnel_target_addr, tunnel_target_protocol, pages_project_id, created_at, updated_at
|
||||
FROM of_proxy_routes;
|
||||
|
||||
DROP TABLE of_proxy_routes;
|
||||
ALTER TABLE of_proxy_routes_new RENAME TO of_proxy_routes;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_proxy_routes_site_name ON of_proxy_routes (site_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_proxy_routes_origin_id ON of_proxy_routes (origin_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_proxy_routes_tunnel_node_id ON of_proxy_routes (tunnel_node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_proxy_routes_pages_project_id ON of_proxy_routes (pages_project_id);
|
||||
|
||||
DROP TABLE IF EXISTS of_managed_domains;
|
||||
|
||||
PRAGMA foreign_keys=ON;
|
||||
|
||||
-- +goose Down
|
||||
-- 仅恢复开发库结构,不回填历史域名/证书数据。
|
||||
|
||||
PRAGMA foreign_keys=OFF;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS of_managed_domains (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
domain TEXT NOT NULL,
|
||||
cert_id INTEGER,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
remark TEXT NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_managed_domains_domain ON of_managed_domains (domain);
|
||||
|
||||
CREATE TABLE of_proxy_routes_old (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_name TEXT NOT NULL DEFAULT '',
|
||||
domain TEXT NOT NULL DEFAULT '',
|
||||
domains TEXT NOT NULL DEFAULT '[]',
|
||||
origin_id INTEGER,
|
||||
origin_url TEXT NOT NULL,
|
||||
origin_host TEXT NOT NULL DEFAULT '',
|
||||
upstreams TEXT NOT NULL DEFAULT '[]',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
enable_https INTEGER NOT NULL DEFAULT 0,
|
||||
cert_id INTEGER,
|
||||
cert_ids TEXT NOT NULL DEFAULT '[]',
|
||||
domain_cert_ids TEXT NOT NULL DEFAULT '[]',
|
||||
redirect_http INTEGER NOT NULL DEFAULT 0,
|
||||
limit_conn_per_server INTEGER NOT NULL DEFAULT 0,
|
||||
limit_conn_per_ip INTEGER NOT NULL DEFAULT 0,
|
||||
limit_rate TEXT NOT NULL DEFAULT '',
|
||||
cache_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
cache_policy TEXT NOT NULL DEFAULT '',
|
||||
cache_rules TEXT NOT NULL DEFAULT '[]',
|
||||
custom_headers TEXT NOT NULL DEFAULT '[]',
|
||||
basic_auth_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
basic_auth_username TEXT NOT NULL DEFAULT '',
|
||||
basic_auth_password TEXT NOT NULL DEFAULT '',
|
||||
remark TEXT NOT NULL DEFAULT '',
|
||||
upstream_type TEXT NOT NULL DEFAULT 'direct',
|
||||
tunnel_node_id INTEGER,
|
||||
tunnel_target_addr TEXT NOT NULL DEFAULT '',
|
||||
tunnel_target_protocol TEXT NOT NULL DEFAULT '',
|
||||
pages_project_id INTEGER,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO of_proxy_routes_old (
|
||||
id, site_name, domain, domains, origin_id, origin_url, origin_host, upstreams, enabled,
|
||||
enable_https, cert_id, cert_ids, domain_cert_ids, redirect_http, limit_conn_per_server,
|
||||
limit_conn_per_ip, limit_rate, cache_enabled, cache_policy, cache_rules, custom_headers,
|
||||
basic_auth_enabled, basic_auth_username, basic_auth_password, remark, upstream_type,
|
||||
tunnel_node_id, tunnel_target_addr, tunnel_target_protocol, pages_project_id, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id, site_name, '', '[]', origin_id, origin_url, origin_host, upstreams, enabled,
|
||||
enable_https, NULL, '[]', '[]', redirect_http, limit_conn_per_server,
|
||||
limit_conn_per_ip, limit_rate, cache_enabled, cache_policy, cache_rules, custom_headers,
|
||||
basic_auth_enabled, basic_auth_username, basic_auth_password, remark, upstream_type,
|
||||
tunnel_node_id, tunnel_target_addr, tunnel_target_protocol, pages_project_id, created_at, updated_at
|
||||
FROM of_proxy_routes;
|
||||
|
||||
DROP TABLE of_proxy_routes;
|
||||
ALTER TABLE of_proxy_routes_old RENAME TO of_proxy_routes;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_proxy_routes_domain ON of_proxy_routes (domain);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_proxy_routes_site_name ON of_proxy_routes (site_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_proxy_routes_origin_id ON of_proxy_routes (origin_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_proxy_routes_tunnel_node_id ON of_proxy_routes (tunnel_node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_proxy_routes_pages_project_id ON of_proxy_routes (pages_project_id);
|
||||
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -83,6 +83,24 @@ func TestMigrateInitializesSQLiteDatabase(t *testing.T) {
|
||||
if !sqliteDB.Migrator().HasTable("of_zone_domains") {
|
||||
t.Error("Migrate() did not create of_zone_domains")
|
||||
}
|
||||
if sqliteDB.Migrator().HasTable("of_managed_domains") {
|
||||
t.Error("Migrate() should drop of_managed_domains after phase-2 cleanup")
|
||||
}
|
||||
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "domain") {
|
||||
t.Error("Migrate() should drop of_proxy_routes.domain after phase-2 cleanup")
|
||||
}
|
||||
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "domains") {
|
||||
t.Error("Migrate() should drop of_proxy_routes.domains after phase-2 cleanup")
|
||||
}
|
||||
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "cert_id") {
|
||||
t.Error("Migrate() should drop of_proxy_routes.cert_id after phase-2 cleanup")
|
||||
}
|
||||
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "cert_ids") {
|
||||
t.Error("Migrate() should drop of_proxy_routes.cert_ids after phase-2 cleanup")
|
||||
}
|
||||
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "domain_cert_ids") {
|
||||
t.Error("Migrate() should drop of_proxy_routes.domain_cert_ids after phase-2 cleanup")
|
||||
}
|
||||
|
||||
zone := model.Zone{Domain: "example.com"}
|
||||
if err := sqliteDB.Create(&zone).Error; err != nil {
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
)
|
||||
|
||||
// ManagedDomain OpenFlare 托管域名实体。
|
||||
type ManagedDomain struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Domain string `json:"domain" gorm:"uniqueIndex;size:255;not null"`
|
||||
CertID *uint `json:"cert_id"`
|
||||
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
||||
Remark string `json:"remark" gorm:"size:255"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// TableName 表名。
|
||||
func (ManagedDomain) TableName() string {
|
||||
return "of_managed_domains"
|
||||
}
|
||||
|
||||
// ListManagedDomains 列出全部托管域名。
|
||||
func ListManagedDomains(ctx context.Context) ([]ManagedDomain, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return nil, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
var domains []ManagedDomain
|
||||
if err := conn.Order("id desc").Find(&domains).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
// ListEnabledManagedDomainsWithCertificate 列出已启用且绑定证书的托管域名。
|
||||
func ListEnabledManagedDomainsWithCertificate(ctx context.Context) ([]ManagedDomain, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return nil, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
var domains []ManagedDomain
|
||||
if err := conn.Where("enabled = ? AND cert_id IS NOT NULL", true).Order("id desc").Find(&domains).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
// GetManagedDomainByID 按 ID 查询托管域名。
|
||||
func GetManagedDomainByID(ctx context.Context, id uint) (*ManagedDomain, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return nil, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
var domain ManagedDomain
|
||||
if err := conn.First(&domain, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain, nil
|
||||
}
|
||||
|
||||
// CreateManagedDomainRecord 创建托管域名。
|
||||
func CreateManagedDomainRecord(ctx context.Context, domain *ManagedDomain) error {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
return conn.Create(domain).Error
|
||||
}
|
||||
|
||||
// SaveManagedDomain 保存托管域名。
|
||||
func SaveManagedDomain(ctx context.Context, domain *ManagedDomain) error {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
return conn.Save(domain).Error
|
||||
}
|
||||
|
||||
// DeleteManagedDomainRecord 删除托管域名。
|
||||
func DeleteManagedDomainRecord(ctx context.Context, id uint) error {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
return conn.Delete(&ManagedDomain{}, id).Error
|
||||
}
|
||||
@@ -11,22 +11,16 @@ import (
|
||||
)
|
||||
|
||||
// ProxyRoute OpenFlare 代理规则实体。
|
||||
// 域名与证书仅通过 of_zone_domains 关联,不再持久化在本表。
|
||||
type ProxyRoute struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
|
||||
// Legacy mirrors are maintained from ZoneDomain bindings until the staged
|
||||
// schema cleanup. They are not route API fields.
|
||||
Domain string `json:"-" gorm:"uniqueIndex;size:255;not null"`
|
||||
Domains string `json:"-" gorm:"type:text;not null;default:'[]'"`
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
|
||||
OriginID *uint `json:"origin_id" gorm:"index"`
|
||||
OriginURL string `json:"origin_url" gorm:"size:2048;not null"`
|
||||
OriginHost string `json:"origin_host" gorm:"size:255"`
|
||||
Upstreams string `json:"upstreams" gorm:"type:text;not null;default:'[]'"`
|
||||
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
||||
EnableHTTPS bool `json:"enable_https" gorm:"column:enable_https;not null;default:false"`
|
||||
CertID *uint `json:"-"`
|
||||
CertIDs string `json:"-" gorm:"type:text;not null;default:'[]'"`
|
||||
DomainCertIDs string `json:"-" gorm:"type:text;not null;default:'[]'"`
|
||||
RedirectHTTP bool `json:"redirect_http" gorm:"not null;default:false"`
|
||||
LimitConnPerServer int `json:"limit_conn_per_server" gorm:"not null;default:0"`
|
||||
LimitConnPerIP int `json:"limit_conn_per_ip" gorm:"not null;default:0"`
|
||||
@@ -81,17 +75,12 @@ func CreateProxyRouteRecord(ctx context.Context, route *ProxyRoute) error {
|
||||
func UpdateProxyRouteRecord(ctx context.Context, route *ProxyRoute) error {
|
||||
return db.DB(ctx).Model(&ProxyRoute{}).Where("id = ?", route.ID).Updates(map[string]any{
|
||||
"site_name": route.SiteName,
|
||||
"domain": route.Domain,
|
||||
"domains": route.Domains,
|
||||
"origin_id": route.OriginID,
|
||||
"origin_url": route.OriginURL,
|
||||
"origin_host": route.OriginHost,
|
||||
"upstreams": route.Upstreams,
|
||||
colEnabled: route.Enabled,
|
||||
"enable_https": route.EnableHTTPS,
|
||||
"cert_id": route.CertID,
|
||||
"cert_ids": route.CertIDs,
|
||||
"domain_cert_ids": route.DomainCertIDs,
|
||||
"redirect_http": route.RedirectHTTP,
|
||||
"limit_conn_per_server": route.LimitConnPerServer,
|
||||
"limit_conn_per_ip": route.LimitConnPerIP,
|
||||
|
||||
Reference in New Issue
Block a user