@@ -82,366 +82,87 @@ def import_yaml_config_to_db(config_path, *, replace_existing: bool = False) ->
|
||||
def load_app_config_from_db() -> AppConfig | None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
return _load_app_config_from_session(session, profile_key=None)
|
||||
profiles = list(
|
||||
session.scalars(
|
||||
select(ProfileORM)
|
||||
.options(
|
||||
selectinload(ProfileORM.groups),
|
||||
selectinload(ProfileORM.rule_links).selectinload(ProfileRuleModuleORM.rule_module),
|
||||
)
|
||||
.order_by(ProfileORM.key)
|
||||
)
|
||||
)
|
||||
sources = list(session.scalars(select(SourceORM).order_by(SourceORM.key)))
|
||||
if not profiles or not sources:
|
||||
return None
|
||||
|
||||
public_path = _setting_value(session, "public_path")
|
||||
primary_profile = profiles[0]
|
||||
|
||||
def load_profile_app_config_from_db(profile_key: str) -> AppConfig | None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
return _load_app_config_from_session(session, profile_key=profile_key)
|
||||
regions: dict[str, RegionConfig] = {}
|
||||
selector_groups: list[ProxyGroupConfig] = []
|
||||
policy_groups: list[ProxyGroupConfig] = []
|
||||
|
||||
for group in sorted(primary_profile.groups, key=lambda item: (item.order_index, item.id)):
|
||||
if not group.enabled:
|
||||
continue
|
||||
if group.group_kind == "region":
|
||||
regions[_slugify(group.name)] = RegionConfig(
|
||||
name=group.name,
|
||||
filter=group.filter_regex or "",
|
||||
tolerance=group.tolerance or 50,
|
||||
)
|
||||
continue
|
||||
target = selector_groups if group.group_kind == "selector" else policy_groups
|
||||
target.append(
|
||||
ProxyGroupConfig(
|
||||
name=group.name,
|
||||
type=group.type,
|
||||
proxies=_json_loads(group.proxies_json, []),
|
||||
filter=group.filter_regex,
|
||||
tolerance=group.tolerance,
|
||||
url=group.url,
|
||||
interval=group.interval,
|
||||
)
|
||||
)
|
||||
|
||||
def list_sources() -> list[SourceORM]:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
return list(session.scalars(select(SourceORM).order_by(SourceORM.key)))
|
||||
|
||||
|
||||
def save_source(
|
||||
*,
|
||||
key: str,
|
||||
enabled: bool,
|
||||
kind: str,
|
||||
url: str,
|
||||
display_name: str | None,
|
||||
headers: dict[str, str],
|
||||
include_regex: str | None,
|
||||
exclude_regex: str | None,
|
||||
prefix: str,
|
||||
suffix: str,
|
||||
cache_ttl_seconds: int | None,
|
||||
) -> None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
source = _get_or_create_source(session, key)
|
||||
source.enabled = enabled
|
||||
source.kind = kind
|
||||
source.url = url
|
||||
source.display_name = display_name
|
||||
source.headers_json = _json_dumps(headers)
|
||||
source.include_regex = include_regex
|
||||
source.exclude_regex = exclude_regex
|
||||
source.prefix = prefix
|
||||
source.suffix = suffix
|
||||
source.cache_ttl_seconds = cache_ttl_seconds
|
||||
session.commit()
|
||||
|
||||
|
||||
def delete_source(key: str) -> None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
source = session.scalar(select(SourceORM).where(SourceORM.key == key))
|
||||
if source is None:
|
||||
return
|
||||
session.execute(delete(ProfileSourceORM).where(ProfileSourceORM.source_id == source.id))
|
||||
session.delete(source)
|
||||
session.commit()
|
||||
|
||||
|
||||
def list_profiles() -> list[ProfileORM]:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
return list(session.scalars(select(ProfileORM).order_by(ProfileORM.key)))
|
||||
|
||||
|
||||
def save_profile(
|
||||
*,
|
||||
key: str,
|
||||
title: str,
|
||||
provider_interval: int,
|
||||
rule_interval: int,
|
||||
test_url: str,
|
||||
test_interval: int,
|
||||
main_policy: str,
|
||||
source_policy: str,
|
||||
mixed_auto_policy: str,
|
||||
manual_policy: str,
|
||||
direct_policy: str,
|
||||
mode: str,
|
||||
allow_lan: bool,
|
||||
ipv6: bool,
|
||||
mixed_port: int | None,
|
||||
socks_port: int | None,
|
||||
log_level: str | None,
|
||||
) -> None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _get_or_create_profile(session, key)
|
||||
profile.title = title
|
||||
profile.provider_interval = provider_interval
|
||||
profile.rule_interval = rule_interval
|
||||
profile.test_url = test_url
|
||||
profile.test_interval = test_interval
|
||||
profile.main_policy = main_policy
|
||||
profile.source_policy = source_policy
|
||||
profile.mixed_auto_policy = mixed_auto_policy
|
||||
profile.manual_policy = manual_policy
|
||||
profile.direct_policy = direct_policy
|
||||
profile.mode = mode
|
||||
profile.allow_lan = allow_lan
|
||||
profile.ipv6 = ipv6
|
||||
profile.mixed_port = mixed_port
|
||||
profile.socks_port = socks_port
|
||||
profile.log_level = log_level
|
||||
session.commit()
|
||||
|
||||
|
||||
def list_profile_rule_bindings(profile_key: str) -> list[dict]:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
return []
|
||||
bindings: list[dict] = []
|
||||
for link in sorted(profile.rule_links, key=lambda item: (item.order_index, item.id)):
|
||||
rules: dict[str, RuleConfig] = {}
|
||||
for link in sorted(primary_profile.rule_links, key=lambda item: (item.order_index, item.id)):
|
||||
if not link.enabled:
|
||||
continue
|
||||
module = link.rule_module
|
||||
bindings.append(
|
||||
{
|
||||
"key": module.key,
|
||||
"enabled": link.enabled,
|
||||
"order_index": link.order_index,
|
||||
"policy": link.policy_override or module.policy,
|
||||
"file_path": module.file_path,
|
||||
"behavior": module.behavior,
|
||||
"format": module.format,
|
||||
}
|
||||
rules[module.key] = RuleConfig(
|
||||
file=module.file_path,
|
||||
behavior=module.behavior,
|
||||
format=module.format,
|
||||
policy=link.policy_override or module.policy,
|
||||
no_resolve=module.no_resolve if link.no_resolve_override is None else link.no_resolve_override,
|
||||
payload=_json_loads(link.payload_override_json, _json_loads(module.payload_json, [])),
|
||||
)
|
||||
return bindings
|
||||
|
||||
|
||||
def list_profile_source_bindings(profile_key: str) -> list[dict]:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
return []
|
||||
bindings: list[dict] = []
|
||||
for link in sorted(profile.source_links, key=lambda item: (item.order_index, item.id)):
|
||||
bindings.append(
|
||||
{
|
||||
"key": link.source.key,
|
||||
"enabled": link.enabled,
|
||||
"order_index": link.order_index,
|
||||
"display_name": link.source.display_name or link.source.key,
|
||||
"kind": link.source.kind,
|
||||
"url": link.source.url,
|
||||
}
|
||||
)
|
||||
return bindings
|
||||
|
||||
|
||||
def update_profile_source_bindings(profile_key: str, rows: list[dict]) -> None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
raise KeyError(f"profile not found: {profile_key}")
|
||||
source_keys = [row["key"] for row in rows]
|
||||
sources = list(session.scalars(select(SourceORM).where(SourceORM.key.in_(source_keys))))
|
||||
source_by_key = {source.key: source for source in sources}
|
||||
session.execute(delete(ProfileSourceORM).where(ProfileSourceORM.profile_id == profile.id))
|
||||
for row in rows:
|
||||
source = source_by_key.get(row["key"])
|
||||
if source is None:
|
||||
continue
|
||||
session.add(
|
||||
ProfileSourceORM(
|
||||
profile_id=profile.id,
|
||||
source_id=source.id,
|
||||
order_index=int(row["order_index"]),
|
||||
enabled=bool(row["enabled"]),
|
||||
return AppConfig(
|
||||
public_path=public_path or None,
|
||||
sources={
|
||||
source.key: SourceConfig(
|
||||
enabled=source.enabled,
|
||||
kind=source.kind,
|
||||
url=source.url,
|
||||
display_name=source.display_name,
|
||||
headers=_json_loads(source.headers_json, {}),
|
||||
include_regex=source.include_regex,
|
||||
exclude_regex=source.exclude_regex,
|
||||
prefix=source.prefix,
|
||||
suffix=source.suffix,
|
||||
cache_ttl_seconds=source.cache_ttl_seconds,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
|
||||
def get_profile_default_source_keys(profile_key: str) -> list[str]:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
return []
|
||||
keys = [
|
||||
link.source.key
|
||||
for link in sorted(profile.source_links, key=lambda item: (item.order_index, item.id))
|
||||
if link.enabled and link.source.enabled and str(link.source.url).strip()
|
||||
]
|
||||
return keys
|
||||
|
||||
|
||||
def list_profile_groups(profile_key: str) -> list[dict]:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
return []
|
||||
rows: list[dict] = []
|
||||
for group in sorted(profile.groups, key=lambda item: (item.order_index, item.id)):
|
||||
rows.append(
|
||||
{
|
||||
"id": group.id,
|
||||
"group_kind": group.group_kind,
|
||||
"name": group.name,
|
||||
"type": group.type,
|
||||
"order_index": group.order_index,
|
||||
"proxies": _json_loads(group.proxies_json, []),
|
||||
"filter_regex": group.filter_regex or "",
|
||||
"tolerance": group.tolerance,
|
||||
"url": group.url or "",
|
||||
"interval": group.interval,
|
||||
"enabled": group.enabled,
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def replace_profile_groups(profile_key: str, rows: list[dict]) -> None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
raise KeyError(f"profile not found: {profile_key}")
|
||||
session.execute(delete(PolicyGroupORM).where(PolicyGroupORM.profile_id == profile.id))
|
||||
for row in rows:
|
||||
session.add(
|
||||
PolicyGroupORM(
|
||||
profile_id=profile.id,
|
||||
group_kind=row["group_kind"],
|
||||
name=row["name"],
|
||||
type=row["type"],
|
||||
order_index=int(row["order_index"]),
|
||||
proxies_json=_json_dumps(row["proxies"]) if row["proxies"] else None,
|
||||
filter_regex=row["filter_regex"] or None,
|
||||
tolerance=row["tolerance"],
|
||||
url=row["url"] or None,
|
||||
interval=row["interval"],
|
||||
enabled=bool(row["enabled"]),
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
|
||||
def update_profile_rule_bindings(profile_key: str, rows: list[dict]) -> None:
|
||||
init_db()
|
||||
with get_session() as session:
|
||||
profile = _load_profile_with_relationships(session, profile_key)
|
||||
if profile is None:
|
||||
raise KeyError(f"profile not found: {profile_key}")
|
||||
module_by_key = {link.rule_module.key: link for link in profile.rule_links}
|
||||
for row in rows:
|
||||
link = module_by_key.get(row["key"])
|
||||
if link is None:
|
||||
continue
|
||||
link.enabled = bool(row["enabled"])
|
||||
link.order_index = int(row["order_index"])
|
||||
policy = str(row["policy"]).strip()
|
||||
link.policy_override = policy or None
|
||||
session.commit()
|
||||
|
||||
|
||||
def _load_app_config_from_session(session: Session, profile_key: str | None) -> AppConfig | None:
|
||||
profiles = list(
|
||||
session.scalars(
|
||||
select(ProfileORM)
|
||||
.options(
|
||||
selectinload(ProfileORM.groups),
|
||||
selectinload(ProfileORM.rule_links).selectinload(ProfileRuleModuleORM.rule_module),
|
||||
)
|
||||
.order_by(ProfileORM.key)
|
||||
for source in sources
|
||||
},
|
||||
rules=rules,
|
||||
clients={profile.key: _profile_to_client_config(profile) for profile in profiles},
|
||||
regions=regions,
|
||||
selector_groups=selector_groups,
|
||||
policy_groups=policy_groups,
|
||||
)
|
||||
)
|
||||
sources = list(session.scalars(select(SourceORM).order_by(SourceORM.key)))
|
||||
if not profiles or not sources:
|
||||
return None
|
||||
|
||||
target_profile = next((profile for profile in profiles if profile.key == profile_key), profiles[0])
|
||||
public_path = _setting_value(session, "public_path")
|
||||
regions, selector_groups, policy_groups = _build_group_configs(target_profile)
|
||||
rules = _build_rule_configs(target_profile)
|
||||
|
||||
return AppConfig(
|
||||
public_path=public_path or None,
|
||||
sources={source.key: _source_to_config(source) for source in sources},
|
||||
rules=rules,
|
||||
clients={profile.key: _profile_to_client_config(profile) for profile in profiles},
|
||||
regions=regions,
|
||||
selector_groups=selector_groups,
|
||||
policy_groups=policy_groups,
|
||||
)
|
||||
|
||||
|
||||
def _source_to_config(source: SourceORM) -> SourceConfig:
|
||||
return SourceConfig(
|
||||
enabled=source.enabled,
|
||||
kind=source.kind,
|
||||
url=source.url,
|
||||
display_name=source.display_name,
|
||||
headers=_json_loads(source.headers_json, {}),
|
||||
include_regex=source.include_regex,
|
||||
exclude_regex=source.exclude_regex,
|
||||
prefix=source.prefix,
|
||||
suffix=source.suffix,
|
||||
cache_ttl_seconds=source.cache_ttl_seconds,
|
||||
)
|
||||
|
||||
|
||||
def _build_group_configs(profile: ProfileORM) -> tuple[dict[str, RegionConfig], list[ProxyGroupConfig], list[ProxyGroupConfig]]:
|
||||
regions: dict[str, RegionConfig] = {}
|
||||
selector_groups: list[ProxyGroupConfig] = []
|
||||
policy_groups: list[ProxyGroupConfig] = []
|
||||
|
||||
for group in sorted(profile.groups, key=lambda item: (item.order_index, item.id)):
|
||||
if not group.enabled:
|
||||
continue
|
||||
if group.group_kind == "region":
|
||||
regions[_slugify(group.name)] = RegionConfig(
|
||||
name=group.name,
|
||||
filter=group.filter_regex or "",
|
||||
tolerance=group.tolerance or 50,
|
||||
)
|
||||
continue
|
||||
target = selector_groups if group.group_kind == "selector" else policy_groups
|
||||
target.append(
|
||||
ProxyGroupConfig(
|
||||
name=group.name,
|
||||
type=group.type,
|
||||
proxies=_json_loads(group.proxies_json, []),
|
||||
filter=group.filter_regex,
|
||||
tolerance=group.tolerance,
|
||||
url=group.url,
|
||||
interval=group.interval,
|
||||
)
|
||||
)
|
||||
return regions, selector_groups, policy_groups
|
||||
|
||||
|
||||
def _build_rule_configs(profile: ProfileORM) -> dict[str, RuleConfig]:
|
||||
rules: dict[str, RuleConfig] = {}
|
||||
for link in sorted(profile.rule_links, key=lambda item: (item.order_index, item.id)):
|
||||
if not link.enabled:
|
||||
continue
|
||||
module = link.rule_module
|
||||
rules[module.key] = RuleConfig(
|
||||
file=module.file_path,
|
||||
behavior=module.behavior,
|
||||
format=module.format,
|
||||
policy=link.policy_override or module.policy,
|
||||
no_resolve=module.no_resolve if link.no_resolve_override is None else link.no_resolve_override,
|
||||
payload=_json_loads(link.payload_override_json, _json_loads(module.payload_json, [])),
|
||||
)
|
||||
return rules
|
||||
|
||||
|
||||
def _load_profile_with_relationships(session: Session, profile_key: str) -> ProfileORM | None:
|
||||
return session.scalar(
|
||||
select(ProfileORM)
|
||||
.where(ProfileORM.key == profile_key)
|
||||
.options(
|
||||
selectinload(ProfileORM.groups),
|
||||
selectinload(ProfileORM.rule_links).selectinload(ProfileRuleModuleORM.rule_module),
|
||||
selectinload(ProfileORM.source_links).selectinload(ProfileSourceORM.source),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _clear_config_tables(session: Session) -> None:
|
||||
|
||||
Reference in New Issue
Block a user