feat: enhance DoH profile management and resolver policy in modules
CI / changes (push) Successful in 6s
CI / openapi (push) Successful in 1m2s
CI / go (push) Successful in 27s
CI / docker-web (push) Successful in 1m27s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 16s
CI / docker-go (push) Successful in 8m5s
CI / changes (push) Successful in 6s
CI / openapi (push) Successful in 1m2s
CI / go (push) Successful in 27s
CI / docker-web (push) Successful in 1m27s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 16s
CI / docker-go (push) Successful in 8m5s
- Added `DohResolverPolicy` schema to OpenAPI documentation, defining policies for domain resolution. - Updated module handling to support multiple DoH profiles via `doh_profile_ids` and introduced `doh_resolver_policy` in the API. - Refactored related functions to accommodate the new DoH profile structure, ensuring backward compatibility with existing `doh_profile_id`. - Enhanced UI components to allow selection and management of DoH profiles and policies in the web interface. - Updated database interactions to handle new fields and ensure proper data normalization.
This commit is contained in:
@@ -114,6 +114,8 @@ type ModulePatch struct {
|
||||
CronExpr *string `json:"cron_expr,omitempty"`
|
||||
DefaultCommunityID *string `json:"default_community_id,omitempty"`
|
||||
DohProfileID *string `json:"doh_profile_id,omitempty"`
|
||||
DohProfileIDs *[]string `json:"doh_profile_ids,omitempty"`
|
||||
DohResolverPolicy *string `json:"doh_resolver_policy,omitempty"`
|
||||
LastRefreshedAt *time.Time `json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package store
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
DohPolicyPrimaryOnly = "primary_only"
|
||||
DohPolicyFailover = "failover"
|
||||
DohPolicyUnion = "union"
|
||||
)
|
||||
|
||||
// NormalizeDohResolverPolicy returns a supported resolver policy name.
|
||||
func NormalizeDohResolverPolicy(policy string) string {
|
||||
switch strings.TrimSpace(policy) {
|
||||
case DohPolicyFailover, DohPolicyUnion:
|
||||
return strings.TrimSpace(policy)
|
||||
default:
|
||||
return DohPolicyPrimaryOnly
|
||||
}
|
||||
}
|
||||
|
||||
// NormalizeDohProfileIDList deduplicates profile ids preserving order.
|
||||
func NormalizeDohProfileIDList(ids []string) []string {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(ids))
|
||||
out := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
out = append(out, id)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// EffectiveDohProfileIDs returns ordered DoH profile ids for a module.
|
||||
func (m *Module) EffectiveDohProfileIDs() []string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
if ids := NormalizeDohProfileIDList(m.DohProfileIDs); len(ids) > 0 {
|
||||
return ids
|
||||
}
|
||||
if m.DohProfileID != nil {
|
||||
if id := strings.TrimSpace(*m.DohProfileID); id != "" {
|
||||
return []string{id}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncLegacyDohProfileID keeps deprecated doh_profile_id aligned with the first profile.
|
||||
func (m *Module) SyncLegacyDohProfileID() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
ids := NormalizeDohProfileIDList(m.DohProfileIDs)
|
||||
if len(ids) == 0 {
|
||||
m.DohProfileID = nil
|
||||
return
|
||||
}
|
||||
first := ids[0]
|
||||
m.DohProfileID = &first
|
||||
}
|
||||
|
||||
// NormalizeModuleDoh fills doh_profile_ids, policy and legacy id from module input.
|
||||
func NormalizeModuleDoh(m *Module) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
ids := NormalizeDohProfileIDList(m.DohProfileIDs)
|
||||
if len(ids) == 0 && m.DohProfileID != nil {
|
||||
if id := strings.TrimSpace(*m.DohProfileID); id != "" {
|
||||
ids = []string{id}
|
||||
}
|
||||
}
|
||||
m.DohProfileIDs = ids
|
||||
m.DohResolverPolicy = NormalizeDohResolverPolicy(m.DohResolverPolicy)
|
||||
m.SyncLegacyDohProfileID()
|
||||
}
|
||||
|
||||
// ApplyModuleDohPatch merges DoH-related fields from patch into mod.
|
||||
func ApplyModuleDohPatch(mod *Module, patch *ModulePatch) {
|
||||
if mod == nil || patch == nil {
|
||||
return
|
||||
}
|
||||
if patch.DohProfileIDs != nil {
|
||||
mod.DohProfileIDs = NormalizeDohProfileIDList(*patch.DohProfileIDs)
|
||||
} else if patch.DohProfileID != nil {
|
||||
v := strings.TrimSpace(*patch.DohProfileID)
|
||||
if v == "" {
|
||||
mod.DohProfileIDs = nil
|
||||
} else {
|
||||
mod.DohProfileIDs = []string{v}
|
||||
}
|
||||
}
|
||||
if patch.DohResolverPolicy != nil {
|
||||
mod.DohResolverPolicy = NormalizeDohResolverPolicy(*patch.DohResolverPolicy)
|
||||
}
|
||||
mod.SyncLegacyDohProfileID()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package store
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizeDohProfileIDList(t *testing.T) {
|
||||
got := NormalizeDohProfileIDList([]string{" a ", "b", "a", "", "b"})
|
||||
if len(got) != 2 || got[0] != "a" || got[1] != "b" {
|
||||
t.Fatalf("unexpected: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyModuleDohPatch(t *testing.T) {
|
||||
mod := &Module{DohProfileIDs: []string{"one"}, DohResolverPolicy: DohPolicyPrimaryOnly}
|
||||
ids := []string{"ru", "eu"}
|
||||
policy := DohPolicyUnion
|
||||
ApplyModuleDohPatch(mod, &ModulePatch{DohProfileIDs: &ids, DohResolverPolicy: &policy})
|
||||
if len(mod.DohProfileIDs) != 2 || mod.DohResolverPolicy != DohPolicyUnion {
|
||||
t.Fatalf("unexpected module doh fields: %+v", mod)
|
||||
}
|
||||
if mod.DohProfileID == nil || *mod.DohProfileID != "ru" {
|
||||
t.Fatalf("legacy id not synced: %+v", mod.DohProfileID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveDohProfileIDs_LegacyField(t *testing.T) {
|
||||
id := "legacy-id"
|
||||
mod := &Module{DohProfileID: &id}
|
||||
got := mod.EffectiveDohProfileIDs()
|
||||
if len(got) != 1 || got[0] != "legacy-id" {
|
||||
t.Fatalf("unexpected: %v", got)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,9 @@ type Module struct {
|
||||
CronExpr string // optional cron for scheduler (display / future use)
|
||||
Priority int
|
||||
DefaultCommunityID *string
|
||||
DohProfileID *string
|
||||
DohProfileID *string // deprecated: first id in DohProfileIDs
|
||||
DohProfileIDs []string
|
||||
DohResolverPolicy string
|
||||
LastRefreshedAt *time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
@@ -31,9 +31,11 @@ func (m *Memory) CreateModule(tenantID string, in *Module) (*Module, error) {
|
||||
RefreshIntervalSec: in.RefreshIntervalSec,
|
||||
CronExpr: in.CronExpr,
|
||||
DefaultCommunityID: in.DefaultCommunityID,
|
||||
DohProfileID: in.DohProfileID,
|
||||
DohProfileIDs: append([]string(nil), in.DohProfileIDs...),
|
||||
DohResolverPolicy: in.DohResolverPolicy,
|
||||
LastRefreshedAt: in.LastRefreshedAt,
|
||||
}
|
||||
NormalizeModuleDoh(mod)
|
||||
m.modules[id] = mod
|
||||
return mod, nil
|
||||
}
|
||||
@@ -71,14 +73,7 @@ func (m *Memory) UpdateModule(tenantID, moduleID string, patch *ModulePatch) (*M
|
||||
mod.DefaultCommunityID = &v
|
||||
}
|
||||
}
|
||||
if patch.DohProfileID != nil {
|
||||
v := strings.TrimSpace(*patch.DohProfileID)
|
||||
if v == "" {
|
||||
mod.DohProfileID = nil
|
||||
} else {
|
||||
mod.DohProfileID = &v
|
||||
}
|
||||
}
|
||||
ApplyModuleDohPatch(mod, patch)
|
||||
if patch.LastRefreshedAt != nil {
|
||||
t := patch.LastRefreshedAt.UTC()
|
||||
mod.LastRefreshedAt = &t
|
||||
@@ -555,8 +550,13 @@ func (m *Memory) DeleteDohProfile(tenantID, id string) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
for _, mod := range m.modules {
|
||||
if mod.DohProfileID != nil && *mod.DohProfileID == id {
|
||||
return ErrInvalidInput
|
||||
if mod == nil || mod.DeletedAt != nil {
|
||||
continue
|
||||
}
|
||||
for _, pid := range mod.EffectiveDohProfileIDs() {
|
||||
if pid == id {
|
||||
return ErrInvalidInput
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(m.dohProfiles, id)
|
||||
|
||||
Reference in New Issue
Block a user