mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
feat(zone): add normalized zone domain schema
This commit is contained in:
@@ -21,6 +21,10 @@ sidebar: false
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- 新增第一阶段 Zone 与正规化 Zone 域名数据库表及路由绑定模型,为后续以稳定 ID 管理网站与域名关联提供基础。
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
- Docker ClickHouse 性能配置改为单文件挂载,避免覆盖镜像内置的 Docker 网络监听配置,导致宿主机无法通过 8123/9000 访问服务。
|
- Docker ClickHouse 性能配置改为单文件挂载,避免覆盖镜像内置的 Docker 网络监听配置,导致宿主机无法通过 8123/9000 访问服务。
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE IF NOT EXISTS of_zones (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
domain VARCHAR(255) NOT NULL,
|
||||||
|
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_zones_domain ON of_zones (domain);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS of_zone_domains (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
zone_id BIGINT NOT NULL,
|
||||||
|
proxy_route_id BIGINT,
|
||||||
|
domain VARCHAR(255) NOT NULL,
|
||||||
|
cert_id BIGINT,
|
||||||
|
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_zone_domains_domain ON of_zone_domains (domain);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_of_zone_domains_zone_id ON of_zone_domains (zone_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_of_zone_domains_proxy_route_id ON of_zone_domains (proxy_route_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_of_zone_domains_cert_id ON of_zone_domains (cert_id);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE IF EXISTS of_zone_domains;
|
||||||
|
DROP TABLE IF EXISTS of_zones;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE IF NOT EXISTS of_zones (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
domain TEXT NOT NULL,
|
||||||
|
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_zones_domain ON of_zones (domain);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS of_zone_domains (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
zone_id INTEGER NOT NULL,
|
||||||
|
proxy_route_id INTEGER,
|
||||||
|
domain TEXT NOT NULL,
|
||||||
|
cert_id INTEGER,
|
||||||
|
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_zone_domains_domain ON of_zone_domains (domain);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_of_zone_domains_zone_id ON of_zone_domains (zone_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_of_zone_domains_proxy_route_id ON of_zone_domains (proxy_route_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_of_zone_domains_cert_id ON of_zone_domains (cert_id);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE IF EXISTS of_zone_domains;
|
||||||
|
DROP TABLE IF EXISTS of_zones;
|
||||||
@@ -76,6 +76,29 @@ func TestMigrateInitializesSQLiteDatabase(t *testing.T) {
|
|||||||
if templateCount != 2 {
|
if templateCount != 2 {
|
||||||
t.Errorf("Migrate() templates count = %d, want %d", templateCount, 2)
|
t.Errorf("Migrate() templates count = %d, want %d", templateCount, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !sqliteDB.Migrator().HasTable("of_zones") {
|
||||||
|
t.Error("Migrate() did not create of_zones")
|
||||||
|
}
|
||||||
|
if !sqliteDB.Migrator().HasTable("of_zone_domains") {
|
||||||
|
t.Error("Migrate() did not create of_zone_domains")
|
||||||
|
}
|
||||||
|
|
||||||
|
zone := model.Zone{Domain: "example.com"}
|
||||||
|
if err := sqliteDB.Create(&zone).Error; err != nil {
|
||||||
|
t.Fatalf("Migrate() create Zone error = %v", err)
|
||||||
|
}
|
||||||
|
if err := sqliteDB.Create(&model.Zone{Domain: zone.Domain}).Error; err == nil {
|
||||||
|
t.Error("Migrate() allowed duplicate of_zones.domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com"}
|
||||||
|
if err := sqliteDB.Create(&domain).Error; err != nil {
|
||||||
|
t.Fatalf("Migrate() create ZoneDomain error = %v", err)
|
||||||
|
}
|
||||||
|
if err := sqliteDB.Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: domain.Domain}).Error; err == nil {
|
||||||
|
t.Error("Migrate() allowed duplicate of_zone_domains.domain")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigrateClearsStaleSystemConfigCache(t *testing.T) {
|
func TestMigrateClearsStaleSystemConfigCache(t *testing.T) {
|
||||||
|
|||||||
@@ -12,38 +12,39 @@ import (
|
|||||||
|
|
||||||
// ProxyRoute OpenFlare 代理规则实体。
|
// ProxyRoute OpenFlare 代理规则实体。
|
||||||
type ProxyRoute struct {
|
type ProxyRoute struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
|
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
|
||||||
Domain string `json:"domain" gorm:"uniqueIndex;size:255;not null"`
|
Domain string `json:"domain" gorm:"uniqueIndex;size:255;not null"`
|
||||||
Domains string `json:"domains" gorm:"type:text;not null;default:'[]'"`
|
Domains string `json:"domains" gorm:"type:text;not null;default:'[]'"`
|
||||||
OriginID *uint `json:"origin_id" gorm:"index"`
|
OriginID *uint `json:"origin_id" gorm:"index"`
|
||||||
OriginURL string `json:"origin_url" gorm:"size:2048;not null"`
|
OriginURL string `json:"origin_url" gorm:"size:2048;not null"`
|
||||||
OriginHost string `json:"origin_host" gorm:"size:255"`
|
OriginHost string `json:"origin_host" gorm:"size:255"`
|
||||||
Upstreams string `json:"upstreams" gorm:"type:text;not null;default:'[]'"`
|
Upstreams string `json:"upstreams" gorm:"type:text;not null;default:'[]'"`
|
||||||
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
||||||
EnableHTTPS bool `json:"enable_https" gorm:"column:enable_https;not null;default:false"`
|
EnableHTTPS bool `json:"enable_https" gorm:"column:enable_https;not null;default:false"`
|
||||||
CertID *uint `json:"cert_id"`
|
CertID *uint `json:"cert_id"`
|
||||||
CertIDs string `json:"cert_ids" gorm:"type:text;not null;default:'[]'"`
|
CertIDs string `json:"cert_ids" gorm:"type:text;not null;default:'[]'"`
|
||||||
DomainCertIDs string `json:"domain_cert_ids" gorm:"type:text;not null;default:'[]'"`
|
DomainCertIDs string `json:"domain_cert_ids" gorm:"type:text;not null;default:'[]'"`
|
||||||
RedirectHTTP bool `json:"redirect_http" gorm:"not null;default:false"`
|
RedirectHTTP bool `json:"redirect_http" gorm:"not null;default:false"`
|
||||||
LimitConnPerServer int `json:"limit_conn_per_server" gorm:"not null;default:0"`
|
LimitConnPerServer int `json:"limit_conn_per_server" gorm:"not null;default:0"`
|
||||||
LimitConnPerIP int `json:"limit_conn_per_ip" gorm:"not null;default:0"`
|
LimitConnPerIP int `json:"limit_conn_per_ip" gorm:"not null;default:0"`
|
||||||
LimitRate string `json:"limit_rate" gorm:"size:32;not null;default:''"`
|
LimitRate string `json:"limit_rate" gorm:"size:32;not null;default:''"`
|
||||||
CacheEnabled bool `json:"cache_enabled" gorm:"not null;default:false"`
|
CacheEnabled bool `json:"cache_enabled" gorm:"not null;default:false"`
|
||||||
CachePolicy string `json:"cache_policy" gorm:"size:32;not null;default:''"`
|
CachePolicy string `json:"cache_policy" gorm:"size:32;not null;default:''"`
|
||||||
CacheRules string `json:"cache_rules" gorm:"type:text;not null;default:'[]'"`
|
CacheRules string `json:"cache_rules" gorm:"type:text;not null;default:'[]'"`
|
||||||
CustomHeaders string `json:"custom_headers" gorm:"type:text;not null;default:'[]'"`
|
CustomHeaders string `json:"custom_headers" gorm:"type:text;not null;default:'[]'"`
|
||||||
BasicAuthEnabled bool `json:"basic_auth_enabled" gorm:"not null;default:false"`
|
BasicAuthEnabled bool `json:"basic_auth_enabled" gorm:"not null;default:false"`
|
||||||
BasicAuthUsername string `json:"basic_auth_username" gorm:"size:255;not null;default:''"`
|
BasicAuthUsername string `json:"basic_auth_username" gorm:"size:255;not null;default:''"`
|
||||||
BasicAuthPassword string `json:"basic_auth_password" gorm:"size:255;not null;default:''"`
|
BasicAuthPassword string `json:"basic_auth_password" gorm:"size:255;not null;default:''"`
|
||||||
Remark string `json:"remark" gorm:"size:255"`
|
Remark string `json:"remark" gorm:"size:255"`
|
||||||
UpstreamType string `json:"upstream_type" gorm:"size:32;not null;default:'direct'"`
|
UpstreamType string `json:"upstream_type" gorm:"size:32;not null;default:'direct'"`
|
||||||
TunnelNodeID *uint `json:"tunnel_node_id" gorm:"index"`
|
TunnelNodeID *uint `json:"tunnel_node_id" gorm:"index"`
|
||||||
TunnelTargetAddr string `json:"tunnel_target_addr" gorm:"size:512"`
|
TunnelTargetAddr string `json:"tunnel_target_addr" gorm:"size:512"`
|
||||||
TunnelTargetProtocol string `json:"tunnel_target_protocol" gorm:"size:16"`
|
TunnelTargetProtocol string `json:"tunnel_target_protocol" gorm:"size:16"`
|
||||||
PagesProjectID *uint `json:"pages_project_id" gorm:"index"`
|
PagesProjectID *uint `json:"pages_project_id" gorm:"index"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
|
ZoneDomains []ZoneDomain `json:"zone_domains,omitempty" gorm:"foreignKey:ProxyRouteID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名。
|
// TableName 表名。
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tableOfZones = "of_zones"
|
||||||
|
tableOfZoneDomains = "of_zone_domains"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errZoneDomainBoundToAnotherRoute = errors.New("zone domain is already bound to another proxy route")
|
||||||
|
|
||||||
|
// Zone OpenFlare 注册根域实体。
|
||||||
|
type Zone struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Domain string `json:"domain" gorm:"uniqueIndex:idx_of_zones_domain;size:255;not null"`
|
||||||
|
Remark string `json:"remark" gorm:"size:255;not null;default:''"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 表名。
|
||||||
|
func (Zone) TableName() string {
|
||||||
|
return tableOfZones
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneDomain OpenFlare Zone 下的明确域名实体。
|
||||||
|
type ZoneDomain struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
ZoneID uint `json:"zone_id" gorm:"not null;index:idx_of_zone_domains_zone_id"`
|
||||||
|
ProxyRouteID *uint `json:"proxy_route_id" gorm:"index:idx_of_zone_domains_proxy_route_id"`
|
||||||
|
Domain string `json:"domain" gorm:"uniqueIndex:idx_of_zone_domains_domain;size:255;not null"`
|
||||||
|
CertID *uint `json:"cert_id" gorm:"index:idx_of_zone_domains_cert_id"`
|
||||||
|
Remark string `json:"remark" gorm:"size:255;not null;default:''"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 表名。
|
||||||
|
func (ZoneDomain) TableName() string {
|
||||||
|
return tableOfZoneDomains
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListZoneDomainsByRouteID returns the domains bound to a proxy route.
|
||||||
|
func ListZoneDomainsByRouteID(ctx context.Context, routeID uint) ([]ZoneDomain, error) {
|
||||||
|
var domains []ZoneDomain
|
||||||
|
if err := db.DB(ctx).Where("proxy_route_id = ?", routeID).Order("id asc").Find(&domains).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceZoneDomainRouteBindings replaces every ZoneDomain binding for a proxy route.
|
||||||
|
func ReplaceZoneDomainRouteBindings(ctx context.Context, routeID uint, domainIDs []uint) error {
|
||||||
|
conn := db.DB(ctx)
|
||||||
|
if conn == nil {
|
||||||
|
return errors.New("database is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn.Transaction(func(tx *gorm.DB) error {
|
||||||
|
var requested []ZoneDomain
|
||||||
|
if len(domainIDs) > 0 {
|
||||||
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Where("id IN ?", domainIDs).
|
||||||
|
Find(&requested).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(requested) != len(uniqueZoneDomainIDs(domainIDs)) {
|
||||||
|
return fmt.Errorf("one or more zone domains do not exist")
|
||||||
|
}
|
||||||
|
for _, domain := range requested {
|
||||||
|
if domain.ProxyRouteID != nil && *domain.ProxyRouteID != routeID {
|
||||||
|
return errZoneDomainBoundToAnotherRoute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current := tx.Model(&ZoneDomain{}).Where("proxy_route_id = ?", routeID)
|
||||||
|
if len(domainIDs) > 0 {
|
||||||
|
current = current.Where("id NOT IN ?", domainIDs)
|
||||||
|
}
|
||||||
|
if err := current.Update("proxy_route_id", nil).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domainIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tx.Model(&ZoneDomain{}).Where("id IN ?", domainIDs).Update("proxy_route_id", routeID).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueZoneDomainIDs(domainIDs []uint) []uint {
|
||||||
|
seen := make(map[uint]struct{}, len(domainIDs))
|
||||||
|
ids := make([]uint, 0, len(domainIDs))
|
||||||
|
for _, id := range domainIDs {
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupZoneTestDB(t *testing.T) *gorm.DB {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
||||||
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, sqliteDB.AutoMigrate(&Zone{}, &ZoneDomain{}))
|
||||||
|
db.SetDB(sqliteDB)
|
||||||
|
t.Cleanup(func() { db.SetDB(nil) })
|
||||||
|
return sqliteDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceZoneDomainRouteBindingsRejectsForeignDomain(t *testing.T) {
|
||||||
|
conn := setupZoneTestDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
zone := Zone{Domain: "example.com"}
|
||||||
|
require.NoError(t, conn.Create(&zone).Error)
|
||||||
|
foreignRouteID := uint(11)
|
||||||
|
domain := ZoneDomain{
|
||||||
|
ZoneID: zone.ID,
|
||||||
|
ProxyRouteID: &foreignRouteID,
|
||||||
|
Domain: "api.example.com",
|
||||||
|
}
|
||||||
|
require.NoError(t, conn.Create(&domain).Error)
|
||||||
|
|
||||||
|
err := ReplaceZoneDomainRouteBindings(ctx, 12, []uint{domain.ID})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
var got ZoneDomain
|
||||||
|
require.NoError(t, conn.First(&got, domain.ID).Error)
|
||||||
|
require.Equal(t, &foreignRouteID, got.ProxyRouteID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceZoneDomainRouteBindingsReplacesCurrentRouteBindings(t *testing.T) {
|
||||||
|
conn := setupZoneTestDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
zone := Zone{Domain: "example.com"}
|
||||||
|
require.NoError(t, conn.Create(&zone).Error)
|
||||||
|
routeID := uint(21)
|
||||||
|
boundDomain := ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &routeID, Domain: "old.example.com"}
|
||||||
|
requestedDomain := ZoneDomain{ZoneID: zone.ID, Domain: "new.example.com"}
|
||||||
|
require.NoError(t, conn.Create(&boundDomain).Error)
|
||||||
|
require.NoError(t, conn.Create(&requestedDomain).Error)
|
||||||
|
|
||||||
|
require.NoError(t, ReplaceZoneDomainRouteBindings(ctx, routeID, []uint{requestedDomain.ID}))
|
||||||
|
|
||||||
|
var domains []ZoneDomain
|
||||||
|
require.NoError(t, conn.Order("id asc").Find(&domains).Error)
|
||||||
|
require.Len(t, domains, 2)
|
||||||
|
require.Nil(t, domains[0].ProxyRouteID)
|
||||||
|
require.Equal(t, &routeID, domains[1].ProxyRouteID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListZoneDomainsByRouteID(t *testing.T) {
|
||||||
|
conn := setupZoneTestDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
zone := Zone{Domain: "example.com"}
|
||||||
|
require.NoError(t, conn.Create(&zone).Error)
|
||||||
|
routeID := uint(31)
|
||||||
|
boundDomain := ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &routeID, Domain: "api.example.com"}
|
||||||
|
unboundDomain := ZoneDomain{ZoneID: zone.ID, Domain: "www.example.com"}
|
||||||
|
require.NoError(t, conn.Create(&boundDomain).Error)
|
||||||
|
require.NoError(t, conn.Create(&unboundDomain).Error)
|
||||||
|
|
||||||
|
domains, err := ListZoneDomainsByRouteID(ctx, routeID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, domains, 1)
|
||||||
|
require.Equal(t, boundDomain.ID, domains[0].ID)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user