重构
This commit is contained in:
142
app/main.py
142
app/main.py
@@ -8,6 +8,10 @@ from fastapi.responses import Response
|
||||
from app.config import get_settings
|
||||
from app.models import RuleConfig, SourceConfig, SourceSnapshot
|
||||
from app.services.bundle_cache import build_bundle_cache_key, load_bundle_cache, save_bundle_cache
|
||||
from app.services.conf_config_store import load_conf_store
|
||||
from app.services.conf_loader import ConfConfigError
|
||||
from app.services.conf_profiles import build_conf_bundle_profile, build_conf_thin_profile, conf_source_to_source_config
|
||||
from app.services.conf_runtime import build_conf_rule_lines, resolve_conf_runtime_plan
|
||||
from app.services.loader import load_app_config
|
||||
from app.services.profiles import build_bundle_profile, build_thin_profile, dump_yaml
|
||||
from app.services.rules import load_rule_text
|
||||
@@ -69,6 +73,37 @@ def _rule_path(rule: RuleConfig):
|
||||
return path
|
||||
|
||||
|
||||
def _load_conf_profile_runtime(profile_key: str):
|
||||
try:
|
||||
base_config, profiles = load_conf_store()
|
||||
except ConfConfigError as exc:
|
||||
raise HTTPException(status_code=500, detail=f"invalid conf config: {exc}") from exc
|
||||
|
||||
profile = profiles.get(profile_key)
|
||||
if profile is None:
|
||||
raise HTTPException(status_code=404, detail="conf profile not found")
|
||||
try:
|
||||
plan = resolve_conf_runtime_plan(
|
||||
profile_key=profile_key,
|
||||
base_config=base_config,
|
||||
profile_config=profile,
|
||||
base_path=settings.conf_base_file,
|
||||
)
|
||||
except ConfConfigError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return base_config, profile, plan
|
||||
|
||||
|
||||
def _conf_source_items(plan) -> list[tuple[str, SourceConfig]]:
|
||||
items: list[tuple[str, SourceConfig]] = []
|
||||
for source in plan.selected_sources:
|
||||
try:
|
||||
items.append((source.key, conf_source_to_source_config(source, base_path=settings.conf_base_file)))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return items
|
||||
|
||||
|
||||
async def _build_quota_headers(source_items: list[tuple[str, SourceConfig]]) -> dict[str, str]:
|
||||
headers: dict[str, str] = {}
|
||||
quota = await get_first_quota(source_items)
|
||||
@@ -161,6 +196,58 @@ async def client_profile(client_type: str, request: Request, sources: str | None
|
||||
return _yaml_response(content, request, headers=headers, filename=f"{client_type}.yaml")
|
||||
|
||||
|
||||
@app.api_route(PUBLIC_PREFIX + "/conf/rules/{profile_key}/{name}.yaml", methods=["GET", "HEAD"])
|
||||
async def conf_rule_file(profile_key: str, name: str, request: Request) -> Response:
|
||||
_, _, plan = _load_conf_profile_runtime(profile_key)
|
||||
module = next((item for item in plan.selected_modules if item.key == name), None)
|
||||
if module is None:
|
||||
raise HTTPException(status_code=404, detail="conf rule not found")
|
||||
path = (settings.conf_base_file.resolve().parent / module.path).resolve()
|
||||
if not path.is_file():
|
||||
raise HTTPException(status_code=404, detail="conf rule file missing")
|
||||
content = load_rule_text(path)
|
||||
return _yaml_response(content, request, filename=f"{profile_key}-{name}.yaml")
|
||||
|
||||
|
||||
@app.api_route(PUBLIC_PREFIX + "/conf/providers/{profile_key}/{name}.yaml", methods=["GET", "HEAD"])
|
||||
async def conf_provider(profile_key: str, name: str, request: Request) -> Response:
|
||||
_, _, plan = _load_conf_profile_runtime(profile_key)
|
||||
source_items = _conf_source_items(plan)
|
||||
source = next((item for item in source_items if item[0] == name), None)
|
||||
if source is None:
|
||||
raise HTTPException(status_code=404, detail="conf provider not found")
|
||||
try:
|
||||
document = await build_provider_document(source[0], source[1])
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.exception("conf provider failed: profile=%s source=%s", profile_key, name)
|
||||
raise HTTPException(status_code=502, detail=f"failed to build conf provider: {exc}") from exc
|
||||
content = dump_provider_yaml(document)
|
||||
headers = await _build_quota_headers([source])
|
||||
return _yaml_response(content, request, headers=headers, filename=f"{profile_key}-{name}.yaml")
|
||||
|
||||
|
||||
@app.api_route(PUBLIC_PREFIX + "/conf/clients/{client_type}/{profile_key}.yaml", methods=["GET", "HEAD"])
|
||||
async def conf_client_profile(client_type: str, profile_key: str, request: Request) -> Response:
|
||||
client = app_config.clients.get(client_type)
|
||||
if client is None:
|
||||
raise HTTPException(status_code=404, detail="client config not found")
|
||||
_, _, plan = _load_conf_profile_runtime(profile_key)
|
||||
source_items = _conf_source_items(plan)
|
||||
content = dump_yaml(
|
||||
build_conf_thin_profile(
|
||||
client_type=client_type,
|
||||
client=client,
|
||||
plan=plan,
|
||||
base_path=settings.conf_base_file,
|
||||
base_url=_base_url(request),
|
||||
public_path=(app_config.public_path or settings.public_path).strip("/"),
|
||||
)
|
||||
)
|
||||
headers = {"profile-update-interval": str(client.provider_interval)}
|
||||
headers.update(await _build_quota_headers(source_items))
|
||||
return _yaml_response(content, request, headers=headers, filename=f"{client_type}-{profile_key}.yaml")
|
||||
|
||||
|
||||
@app.api_route(PUBLIC_PREFIX + "/bundle/{client_type}.yaml", methods=["GET", "HEAD"])
|
||||
async def bundle_profile(
|
||||
client_type: str,
|
||||
@@ -215,3 +302,58 @@ async def bundle_profile(
|
||||
headers={key: value for key, value in headers.items() if key != "X-Sub-Provider-Bundle-Cache"},
|
||||
)
|
||||
return _yaml_response(content, request, headers=headers, filename=f"bundle-{client_type}.yaml")
|
||||
|
||||
|
||||
@app.api_route(PUBLIC_PREFIX + "/conf/bundle/{client_type}/{profile_key}.yaml", methods=["GET", "HEAD"])
|
||||
async def conf_bundle_profile(client_type: str, profile_key: str, request: Request, force_refresh: bool = Query(default=False)) -> Response:
|
||||
client = app_config.clients.get(client_type)
|
||||
if client is None:
|
||||
raise HTTPException(status_code=404, detail="client config not found")
|
||||
_, _, plan = _load_conf_profile_runtime(profile_key)
|
||||
source_items = _conf_source_items(plan)
|
||||
cache_key = build_bundle_cache_key(
|
||||
client_type=f"conf-{client_type}-{profile_key}",
|
||||
source_names=[name for name, _ in source_items],
|
||||
)
|
||||
if not force_refresh:
|
||||
cached = load_bundle_cache(
|
||||
cache_dir=settings.bundle_cache_dir,
|
||||
cache_key=cache_key,
|
||||
ttl_seconds=settings.bundle_cache_ttl_seconds,
|
||||
)
|
||||
if cached is not None:
|
||||
headers = {
|
||||
"profile-update-interval": str(client.provider_interval),
|
||||
"X-Sub-Provider-Bundle-Cache": "HIT",
|
||||
}
|
||||
headers.update(cached.headers)
|
||||
return _yaml_response(content=cached.content, request=request, headers=headers, filename=f"bundle-{client_type}-{profile_key}.yaml")
|
||||
|
||||
try:
|
||||
snapshots = await build_source_snapshots(source_items)
|
||||
rules = build_conf_rule_lines(plan, base_path=settings.conf_base_file)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.exception("conf_bundle_profile failed: client=%s profile=%s", client_type, profile_key)
|
||||
raise HTTPException(status_code=502, detail=f"failed to build conf bundle: {exc}") from exc
|
||||
|
||||
content = dump_yaml(
|
||||
build_conf_bundle_profile(
|
||||
client_type=client_type,
|
||||
client=client,
|
||||
plan=plan,
|
||||
snapshots=snapshots,
|
||||
rules=rules,
|
||||
)
|
||||
)
|
||||
headers = {
|
||||
"profile-update-interval": str(client.provider_interval),
|
||||
"X-Sub-Provider-Bundle-Cache": "BYPASS" if force_refresh else "MISS",
|
||||
}
|
||||
headers.update(_quota_headers_from_snapshots(snapshots))
|
||||
save_bundle_cache(
|
||||
cache_dir=settings.bundle_cache_dir,
|
||||
cache_key=cache_key,
|
||||
content=content,
|
||||
headers={key: value for key, value in headers.items() if key != "X-Sub-Provider-Bundle-Cache"},
|
||||
)
|
||||
return _yaml_response(content, request, headers=headers, filename=f"bundle-{client_type}-{profile_key}.yaml")
|
||||
|
||||
Reference in New Issue
Block a user