fix pylance problems
This commit is contained in:
1 parent
bd7552f23b
commit
c744bc0bc3
26 files changed
+405
-251
No files matched your search
@@ -2,7 +2,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""认证控制器。"""
|
||||
|
||||
from flask import Response, jsonify, make_response, redirect, render_template, request
|
||||
from typing import Any
|
||||
|
||||
from flask import jsonify, make_response, redirect, render_template, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from ..application.app_context import AppContext
|
||||
from ..services import AuthenticationService
|
||||
|
||||
@@ -17,15 +21,20 @@ class AuthenticationController:
|
||||
self._auth_service = auth_service
|
||||
self._register_routes()
|
||||
|
||||
@staticmethod
|
||||
def _get_request_payload() -> dict[str, Any]:
|
||||
payload = request.get_json(silent=True)
|
||||
if isinstance(payload, dict):
|
||||
return dict(payload)
|
||||
return {}
|
||||
|
||||
def _register_routes(self) -> None:
|
||||
"""注册认证相关路由。"""
|
||||
self._app.route('/login')(self.login_page)
|
||||
self._app.route('/api/login', methods=['POST'])(self.api_login)
|
||||
self._app.route('/logout')(self.logout)
|
||||
self._app.route('/api/logout', methods=['POST'])(self.api_logout)
|
||||
|
||||
def login_page(self) -> Response:
|
||||
"""返回登录页,已登录用户会被重定向到首页。"""
|
||||
def login_page(self) -> ResponseReturnValue:
|
||||
if not self._auth_service.is_auth_enabled():
|
||||
return redirect('/')
|
||||
|
||||
@@ -35,47 +44,44 @@ class AuthenticationController:
|
||||
|
||||
return render_template('login.html')
|
||||
|
||||
def api_login(self) -> Response:
|
||||
"""处理登录请求并写入 session cookie。"""
|
||||
def api_login(self) -> ResponseReturnValue:
|
||||
if not self._auth_service.is_auth_enabled():
|
||||
return jsonify({'message': 'Authentication not enabled'}), 200
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
data = self._get_request_payload()
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
|
||||
if not username or not password:
|
||||
self._logger.warning("Login rejected: missing username or password")
|
||||
if not isinstance(username, str) or not username or not isinstance(password, str) or not password:
|
||||
self._logger.warning('Login rejected: missing username or password')
|
||||
return jsonify({'error': 'Username and password are required'}), 400
|
||||
|
||||
if not self._auth_service.authenticate(username, password):
|
||||
self._logger.warning(f"Login failed: invalid credentials for username={username!r}")
|
||||
self._logger.warning('Login failed: invalid credentials for username=%r', username)
|
||||
return jsonify({'error': 'Invalid username or password'}), 401
|
||||
|
||||
session_token = self._auth_service.create_session(username)
|
||||
self._logger.info(f"Login succeeded: username={username!r}")
|
||||
self._logger.info('Login succeeded: username=%r', username)
|
||||
response = make_response(jsonify({'message': 'Login successful', 'username': username}))
|
||||
cookie_settings = self._auth_service.get_cookie_settings()
|
||||
response.set_cookie('session_token', session_token, **cookie_settings)
|
||||
return response
|
||||
|
||||
def logout(self) -> Response:
|
||||
"""处理页面登出并清理 cookie。"""
|
||||
def logout(self) -> ResponseReturnValue:
|
||||
session_token = request.cookies.get('session_token')
|
||||
if session_token:
|
||||
self._auth_service.destroy_session(session_token)
|
||||
self._logger.info("Logout succeeded via page route")
|
||||
self._logger.info('Logout succeeded via page route')
|
||||
|
||||
response = make_response(redirect('/login'))
|
||||
response.delete_cookie('session_token')
|
||||
return response
|
||||
|
||||
def api_logout(self) -> Response:
|
||||
"""处理 API 登出并清理 cookie。"""
|
||||
def api_logout(self) -> ResponseReturnValue:
|
||||
session_token = request.cookies.get('session_token')
|
||||
if session_token:
|
||||
self._auth_service.destroy_session(session_token)
|
||||
self._logger.info("Logout succeeded via API route")
|
||||
self._logger.info('Logout succeeded via API route')
|
||||
|
||||
response = make_response(jsonify({'message': 'Logout successful'}))
|
||||
response.delete_cookie('session_token')
|
||||
|
||||
@@ -3,19 +3,24 @@
|
||||
"""认证装饰器。"""
|
||||
|
||||
from functools import wraps
|
||||
from typing import Callable
|
||||
from typing import Callable, ParamSpec
|
||||
|
||||
from flask import jsonify, redirect, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from ..services import AuthenticationService
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
def require_authentication(auth_service: AuthenticationService):
|
||||
|
||||
def require_authentication(
|
||||
auth_service: AuthenticationService,
|
||||
) -> Callable[[Callable[P, ResponseReturnValue]], Callable[P, ResponseReturnValue]]:
|
||||
"""生成认证校验装饰器。"""
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
def decorator(func: Callable[P, ResponseReturnValue]) -> Callable[P, ResponseReturnValue]:
|
||||
@wraps(func)
|
||||
def decorated_function(*args, **kwargs):
|
||||
def decorated_function(*args: P.args, **kwargs: P.kwargs) -> ResponseReturnValue:
|
||||
if not auth_service.is_auth_enabled():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Provider 管理 API。"""
|
||||
|
||||
from flask import Response, jsonify, request
|
||||
from typing import Any
|
||||
|
||||
from flask import jsonify, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from ..application.app_context import AppContext
|
||||
from ..services import (
|
||||
@@ -16,7 +19,7 @@ from .decorators import require_authentication
|
||||
|
||||
|
||||
class ProviderController:
|
||||
"""处理 provider 配置的增删改查、模型拉取与配置开关更新。"""
|
||||
"""处理 provider 配置、认证分组与相关管理 API。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -36,6 +39,19 @@ class ProviderController:
|
||||
self._auth_service = auth_service
|
||||
self._register_routes()
|
||||
|
||||
@staticmethod
|
||||
def _get_request_payload() -> dict[str, Any]:
|
||||
payload = request.get_json(silent=True)
|
||||
if isinstance(payload, dict):
|
||||
return dict(payload)
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _coerce_name_list(value: Any) -> list[str]:
|
||||
if not isinstance(value, list):
|
||||
raise ValueError('Provider names must be a non-empty list')
|
||||
return [str(item) for item in value]
|
||||
|
||||
def _register_routes(self) -> None:
|
||||
auth = require_authentication(self._auth_service)
|
||||
|
||||
@@ -75,14 +91,14 @@ class ProviderController:
|
||||
auth(self.restore_auth_group_entry)
|
||||
)
|
||||
|
||||
def get_providers(self) -> Response:
|
||||
def get_providers(self) -> ResponseReturnValue:
|
||||
try:
|
||||
return jsonify(self._provider_service.list_providers())
|
||||
except Exception as exc:
|
||||
self._logger.error('Error getting providers: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_provider(self, name: str) -> Response:
|
||||
def get_provider(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
provider = self._provider_service.get_provider(name)
|
||||
if provider is None:
|
||||
@@ -92,9 +108,9 @@ class ProviderController:
|
||||
self._logger.error('Error getting provider: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def create_provider(self) -> Response:
|
||||
def create_provider(self) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
provider = self._provider_service.create_provider(payload)
|
||||
self._logger.info('Provider created: %s', provider.get('name'))
|
||||
return jsonify(provider), 201
|
||||
@@ -104,9 +120,9 @@ class ProviderController:
|
||||
self._logger.error('Error creating provider: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def update_provider(self, name: str) -> Response:
|
||||
def update_provider(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
provider = self._provider_service.update_provider(name, payload)
|
||||
self._logger.info('Provider updated: %s -> %s', name, provider.get('name'))
|
||||
return jsonify(provider)
|
||||
@@ -118,7 +134,7 @@ class ProviderController:
|
||||
self._logger.error('Error updating provider: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def delete_provider(self, name: str) -> Response:
|
||||
def delete_provider(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._provider_service.delete_provider(name)
|
||||
self._logger.info('Provider deleted: %s', name)
|
||||
@@ -131,7 +147,7 @@ class ProviderController:
|
||||
self._logger.error('Error deleting provider: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def disable_provider(self, name: str) -> Response:
|
||||
def disable_provider(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
provider = self._provider_service.set_provider_enabled(name, enabled=False)
|
||||
self._logger.info('Provider disabled: %s', name)
|
||||
@@ -144,7 +160,7 @@ class ProviderController:
|
||||
self._logger.error('Error disabling provider: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def enable_provider(self, name: str) -> Response:
|
||||
def enable_provider(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
provider = self._provider_service.set_provider_enabled(name, enabled=True)
|
||||
self._logger.info('Provider enabled: %s', name)
|
||||
@@ -157,11 +173,11 @@ class ProviderController:
|
||||
self._logger.error('Error enabling provider: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def batch_providers(self) -> Response:
|
||||
def batch_providers(self) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
action = str(payload.get('action') or '').strip().lower()
|
||||
names = payload.get('names')
|
||||
names = self._coerce_name_list(payload.get('names'))
|
||||
if action == 'enable':
|
||||
result = self._provider_service.batch_set_provider_enabled(names, enabled=True)
|
||||
elif action == 'disable':
|
||||
@@ -181,14 +197,14 @@ class ProviderController:
|
||||
self._logger.error('Error applying provider batch action: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_auth_groups(self) -> Response:
|
||||
def get_auth_groups(self) -> ResponseReturnValue:
|
||||
try:
|
||||
return jsonify(self._auth_group_service.list_auth_groups())
|
||||
except Exception as exc:
|
||||
self._logger.error('Error getting auth groups: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_auth_group(self, name: str) -> Response:
|
||||
def get_auth_group(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
auth_group = self._auth_group_service.get_auth_group(name)
|
||||
if auth_group is None:
|
||||
@@ -198,9 +214,9 @@ class ProviderController:
|
||||
self._logger.error('Error getting auth group: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def create_auth_group(self) -> Response:
|
||||
def create_auth_group(self) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
auth_group = self._auth_group_service.create_auth_group(payload)
|
||||
self._logger.info('Auth group created: %s', auth_group.get('name'))
|
||||
return jsonify(auth_group), 201
|
||||
@@ -210,10 +226,11 @@ class ProviderController:
|
||||
self._logger.error('Error creating auth group: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def import_auth_group_entries(self) -> Response:
|
||||
def import_auth_group_entries(self) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
entries = self._auth_group_service.import_auth_entries(payload.get('yaml', ''))
|
||||
payload = self._get_request_payload()
|
||||
yaml_text = str(payload.get('yaml', '') or '')
|
||||
entries = self._auth_group_service.import_auth_entries(yaml_text)
|
||||
return jsonify({'entries': entries})
|
||||
except ValueError as exc:
|
||||
return jsonify({'error': str(exc)}), 400
|
||||
@@ -221,9 +238,9 @@ class ProviderController:
|
||||
self._logger.error('Error importing auth group entries: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def update_auth_group(self, name: str) -> Response:
|
||||
def update_auth_group(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
auth_group = self._auth_group_service.update_auth_group(name, payload)
|
||||
self._logger.info('Auth group updated: %s -> %s', name, auth_group.get('name'))
|
||||
return jsonify(auth_group)
|
||||
@@ -235,7 +252,7 @@ class ProviderController:
|
||||
self._logger.error('Error updating auth group: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def delete_auth_group(self, name: str) -> Response:
|
||||
def delete_auth_group(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.delete_auth_group(name)
|
||||
self._logger.info('Auth group deleted: %s', name)
|
||||
@@ -248,7 +265,7 @@ class ProviderController:
|
||||
self._logger.error('Error deleting auth group: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_auth_group_runtime(self, name: str) -> Response:
|
||||
def get_auth_group_runtime(self, name: str) -> ResponseReturnValue:
|
||||
try:
|
||||
return jsonify(self._auth_group_service.get_auth_group_runtime(name))
|
||||
except ValueError as exc:
|
||||
@@ -257,7 +274,7 @@ class ProviderController:
|
||||
self._logger.error('Error getting auth group runtime: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def clear_auth_group_entry_cooldown(self, name: str, entry_id: str) -> Response:
|
||||
def clear_auth_group_entry_cooldown(self, name: str, entry_id: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.clear_entry_cooldown(name, entry_id)
|
||||
self._logger.info('Auth group entry cooldown cleared: %s/%s', name, entry_id)
|
||||
@@ -270,7 +287,7 @@ class ProviderController:
|
||||
self._logger.error('Error clearing auth group entry cooldown: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def disable_auth_group_entry(self, name: str, entry_id: str) -> Response:
|
||||
def disable_auth_group_entry(self, name: str, entry_id: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.set_entry_disabled(name, entry_id, disabled=True)
|
||||
self._logger.info('Auth group entry disabled: %s/%s', name, entry_id)
|
||||
@@ -283,7 +300,7 @@ class ProviderController:
|
||||
self._logger.error('Error disabling auth group entry: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def enable_auth_group_entry(self, name: str, entry_id: str) -> Response:
|
||||
def enable_auth_group_entry(self, name: str, entry_id: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.set_entry_disabled(name, entry_id, disabled=False)
|
||||
self._logger.info('Auth group entry enabled: %s/%s', name, entry_id)
|
||||
@@ -296,7 +313,7 @@ class ProviderController:
|
||||
self._logger.error('Error enabling auth group entry: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def reset_auth_group_entry_minute_usage(self, name: str, entry_id: str) -> Response:
|
||||
def reset_auth_group_entry_minute_usage(self, name: str, entry_id: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.reset_entry_minute_usage(name, entry_id)
|
||||
self._logger.info('Auth group entry minute usage reset: %s/%s', name, entry_id)
|
||||
@@ -309,7 +326,7 @@ class ProviderController:
|
||||
self._logger.error('Error resetting auth group entry minute usage: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def reset_auth_group_entry_runtime(self, name: str, entry_id: str) -> Response:
|
||||
def reset_auth_group_entry_runtime(self, name: str, entry_id: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.reset_entry_runtime(name, entry_id)
|
||||
self._logger.info('Auth group entry runtime reset: %s/%s', name, entry_id)
|
||||
@@ -322,7 +339,7 @@ class ProviderController:
|
||||
self._logger.error('Error resetting auth group entry runtime: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def restore_auth_group_entry(self, name: str, entry_id: str) -> Response:
|
||||
def restore_auth_group_entry(self, name: str, entry_id: str) -> ResponseReturnValue:
|
||||
try:
|
||||
self._auth_group_service.restore_entry(name, entry_id)
|
||||
self._logger.info('Auth group entry restored: %s/%s', name, entry_id)
|
||||
@@ -335,7 +352,7 @@ class ProviderController:
|
||||
self._logger.error('Error restoring auth group entry: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def fetch_models(self) -> Response:
|
||||
def fetch_models(self) -> ResponseReturnValue:
|
||||
try:
|
||||
result = self._model_discovery_service.fetch_models_preview(
|
||||
api=request.args.get('api', ''),
|
||||
@@ -356,9 +373,9 @@ class ProviderController:
|
||||
self._logger.error('Error fetching provider models: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def update_chat_whitelist(self) -> Response:
|
||||
def update_chat_whitelist(self) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
enabled = self._settings_service.update_chat_whitelist_enabled(payload.get('enabled'))
|
||||
self._logger.info('Chat whitelist updated: enabled=%s', enabled)
|
||||
return jsonify({'enabled': enabled})
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from flask import Response, jsonify, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from ..application.app_context import AppContext
|
||||
from ..config import ConfigManager, ProviderManager
|
||||
@@ -56,7 +57,7 @@ class ProxyController:
|
||||
*,
|
||||
error_format: str,
|
||||
) -> tuple[Optional[Dict[str, Any]], Optional[tuple[Response, int]]]:
|
||||
"""在开启白名单时解析当前请求对应用户。"""
|
||||
"""在启用白名单时解析当前请求对应的用户。"""
|
||||
if not self._is_whitelist_required():
|
||||
return None, None
|
||||
|
||||
@@ -151,21 +152,21 @@ class ProxyController:
|
||||
return normalized[0]
|
||||
return ", ".join(normalized)
|
||||
|
||||
def chat_completions(self) -> Response:
|
||||
def chat_completions(self) -> ResponseReturnValue:
|
||||
return self._proxy_completion_request(
|
||||
route_name="chat_completions",
|
||||
expected_target_formats=("openai_chat",),
|
||||
inspect_stream_usage=True,
|
||||
)
|
||||
|
||||
def responses(self) -> Response:
|
||||
def responses(self) -> ResponseReturnValue:
|
||||
return self._proxy_completion_request(
|
||||
route_name="responses",
|
||||
expected_target_formats=("openai_responses", "codex"),
|
||||
inspect_stream_usage=False,
|
||||
)
|
||||
|
||||
def messages(self) -> Response:
|
||||
def messages(self) -> ResponseReturnValue:
|
||||
return self._proxy_completion_request(
|
||||
route_name="messages",
|
||||
expected_target_formats=("claude_chat",),
|
||||
@@ -179,7 +180,7 @@ class ProxyController:
|
||||
expected_target_formats: Iterable[str],
|
||||
inspect_stream_usage: bool,
|
||||
error_format: Optional[str] = None,
|
||||
) -> Response:
|
||||
) -> ResponseReturnValue:
|
||||
normalized_expected_target_formats = tuple(
|
||||
str(item or "").strip().lower() for item in expected_target_formats if str(item or "").strip()
|
||||
)
|
||||
@@ -196,10 +197,10 @@ class ProxyController:
|
||||
if denial_response is not None:
|
||||
return denial_response
|
||||
|
||||
request_data = request.get_json(silent=True)
|
||||
if request_data is None:
|
||||
request_data = {}
|
||||
if not isinstance(request_data, dict):
|
||||
raw_request_data = request.get_json(silent=True)
|
||||
if raw_request_data is None:
|
||||
request_data: Dict[str, Any] = {}
|
||||
elif not isinstance(raw_request_data, dict):
|
||||
self._logger.warning("Proxy rejected: request body is not a JSON object route=%s", route_name)
|
||||
return self._error_response(
|
||||
"Request body must be a JSON object",
|
||||
@@ -208,9 +209,11 @@ class ProxyController:
|
||||
code="invalid_request_body",
|
||||
error_format=resolved_error_format,
|
||||
)
|
||||
else:
|
||||
request_data = dict(raw_request_data)
|
||||
|
||||
request_data = dict(request_data)
|
||||
if "model" not in request_data:
|
||||
model_name_value = request_data.get("model")
|
||||
if not isinstance(model_name_value, str) or not model_name_value.strip():
|
||||
self._logger.warning("Proxy rejected: missing model in request body route=%s", route_name)
|
||||
return self._error_response(
|
||||
"Missing 'model' in request body",
|
||||
@@ -220,7 +223,7 @@ class ProxyController:
|
||||
error_format=resolved_error_format,
|
||||
)
|
||||
|
||||
model_name = request_data["model"]
|
||||
model_name = model_name_value.strip()
|
||||
provider = self._provider_manager.get_provider_for_model(model_name)
|
||||
if not provider:
|
||||
self._logger.warning("Proxy rejected: unknown model=%r route=%s", model_name, route_name)
|
||||
@@ -390,7 +393,7 @@ class ProxyController:
|
||||
error_format=resolved_error_format,
|
||||
)
|
||||
|
||||
def list_models(self) -> Response:
|
||||
def list_models(self) -> ResponseReturnValue:
|
||||
try:
|
||||
client_ip = normalize_ip(request.remote_addr)
|
||||
user, denial_response = self._get_authorized_user_for_request(
|
||||
@@ -453,3 +456,6 @@ class ProxyController:
|
||||
"upgrade",
|
||||
}
|
||||
return {k: v for k, v in headers.items() if k.lower() not in excluded}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""用户控制器。"""
|
||||
|
||||
from flask import Response, jsonify, request
|
||||
from typing import Any
|
||||
|
||||
from flask import jsonify, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from ..application.app_context import AppContext
|
||||
from ..services import AuthenticationService, UserService
|
||||
@@ -22,8 +25,14 @@ class UserController:
|
||||
self._auth_service = auth_service
|
||||
self._register_routes()
|
||||
|
||||
@staticmethod
|
||||
def _get_request_payload() -> dict[str, Any]:
|
||||
payload = request.get_json(silent=True)
|
||||
if isinstance(payload, dict):
|
||||
return dict(payload)
|
||||
return {}
|
||||
|
||||
def _register_routes(self) -> None:
|
||||
"""注册用户管理路由。"""
|
||||
auth = require_authentication(self._auth_service)
|
||||
|
||||
self._app.route('/api/users', methods=['GET'])(auth(self.get_users))
|
||||
@@ -34,14 +43,13 @@ class UserController:
|
||||
self._app.route('/api/users/<int:user_id>', methods=['DELETE'])(auth(self.delete_user))
|
||||
self._app.route('/api/users/<int:user_id>/toggle', methods=['POST'])(auth(self.toggle_user))
|
||||
|
||||
def get_users(self) -> Response:
|
||||
"""分页查询用户列表。"""
|
||||
def get_users(self) -> ResponseReturnValue:
|
||||
try:
|
||||
page = request.args.get('page', 1, type=int)
|
||||
page_size = request.args.get('page_size', 50, type=int)
|
||||
keyword = (request.args.get('keyword', '', type=str) or '').strip()
|
||||
self._logger.debug(
|
||||
"List users requested: page=%s, page_size=%s, keyword=%r",
|
||||
'List users requested: page=%s, page_size=%s, keyword=%r',
|
||||
page,
|
||||
page_size,
|
||||
keyword,
|
||||
@@ -50,71 +58,84 @@ class UserController:
|
||||
users = self._user_service.get_users(page=page, page_size=page_size, keyword=keyword)
|
||||
total = self._user_service.get_total_users_count(keyword=keyword)
|
||||
|
||||
return jsonify({
|
||||
'users': users,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'total_pages': (total + page_size - 1) // page_size,
|
||||
'keyword': keyword,
|
||||
'available_models': self._user_service.get_available_models(),
|
||||
})
|
||||
return jsonify(
|
||||
{
|
||||
'users': users,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'total_pages': (total + page_size - 1) // page_size,
|
||||
'keyword': keyword,
|
||||
'available_models': self._user_service.get_available_models(),
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error getting users: {exc}')
|
||||
self._logger.error('Error getting users: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_user(self, user_id: int) -> Response:
|
||||
"""查询单个用户。"""
|
||||
def get_user(self, user_id: int) -> ResponseReturnValue:
|
||||
try:
|
||||
user = self._user_service.get_user_by_id(user_id)
|
||||
if not user:
|
||||
self._logger.warning(f"Get user failed: user_id={user_id} not found")
|
||||
self._logger.warning('Get user failed: user_id=%s not found', user_id)
|
||||
return jsonify({'error': 'User not found'}), 404
|
||||
return jsonify(user)
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error getting user: {exc}')
|
||||
self._logger.error('Error getting user: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def create_user(self) -> Response:
|
||||
"""创建用户。"""
|
||||
def create_user(self) -> ResponseReturnValue:
|
||||
try:
|
||||
data = request.get_json(silent=True) or {}
|
||||
data = self._get_request_payload()
|
||||
username = data.get('username')
|
||||
ip_address = data.get('ip_address')
|
||||
|
||||
if not username:
|
||||
self._logger.warning("Create user rejected: username is empty")
|
||||
if not isinstance(username, str) or not username.strip():
|
||||
self._logger.warning('Create user rejected: username is empty')
|
||||
return jsonify({'error': 'Username is required'}), 400
|
||||
if not ip_address:
|
||||
self._logger.warning("Create user rejected: ip_address is empty")
|
||||
if not isinstance(ip_address, str) or not ip_address.strip():
|
||||
self._logger.warning('Create user rejected: ip_address is empty')
|
||||
return jsonify({'error': 'IP address is required'}), 400
|
||||
|
||||
normalized_username = username.strip()
|
||||
normalized_ip = normalize_ip(ip_address)
|
||||
if not is_valid_ip(normalized_ip):
|
||||
self._logger.warning(f"Create user rejected: invalid ip_address={ip_address!r}")
|
||||
self._logger.warning('Create user rejected: invalid ip_address=%r', ip_address)
|
||||
return jsonify({'error': 'Invalid IP address'}), 400
|
||||
|
||||
user_id = self._user_service.create_user(username, normalized_ip)
|
||||
user_id = self._user_service.create_user(normalized_username, normalized_ip)
|
||||
if not user_id:
|
||||
self._logger.warning(f"Create user failed: username={username!r}, ip={normalized_ip}")
|
||||
self._logger.warning('Create user failed: username=%r, ip=%s', normalized_username, normalized_ip)
|
||||
return jsonify({'error': 'Failed to create user or IP already exists'}), 400
|
||||
|
||||
self._logger.info(f"Create user succeeded: user_id={user_id}, username={username!r}, ip={normalized_ip}")
|
||||
self._logger.info(
|
||||
'Create user succeeded: user_id=%s, username=%r, ip=%s',
|
||||
user_id,
|
||||
normalized_username,
|
||||
normalized_ip,
|
||||
)
|
||||
return jsonify({'id': user_id, 'message': 'User created successfully'}), 201
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error creating user: {exc}')
|
||||
self._logger.error('Error creating user: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def update_user(self, user_id: int) -> Response:
|
||||
"""更新用户信息。"""
|
||||
def update_user(self, user_id: int) -> ResponseReturnValue:
|
||||
try:
|
||||
data = request.get_json(silent=True)
|
||||
data = self._get_request_payload()
|
||||
if not data:
|
||||
self._logger.warning(f"Update user rejected: no payload, user_id={user_id}")
|
||||
self._logger.warning('Update user rejected: no payload, user_id=%s', user_id)
|
||||
return jsonify({'error': 'No data provided'}), 400
|
||||
|
||||
username = data.get('username')
|
||||
ip_address = data.get('ip_address')
|
||||
username_value = data.get('username')
|
||||
if username_value is not None and not isinstance(username_value, str):
|
||||
return jsonify({'error': 'Username must be a string'}), 400
|
||||
username = username_value
|
||||
|
||||
ip_address_value = data.get('ip_address')
|
||||
if ip_address_value is not None and not isinstance(ip_address_value, str):
|
||||
return jsonify({'error': 'IP address must be a string'}), 400
|
||||
ip_address = ip_address_value
|
||||
|
||||
whitelist_access_enabled = data.get('whitelist_access_enabled')
|
||||
model_permissions_provided = 'model_permissions' in data
|
||||
model_permissions = data.get('model_permissions')
|
||||
@@ -123,7 +144,7 @@ class UserController:
|
||||
if ip_address is not None:
|
||||
normalized_ip = normalize_ip(ip_address)
|
||||
if not is_valid_ip(normalized_ip):
|
||||
self._logger.warning(f"Update user rejected: invalid ip={ip_address!r}, user_id={user_id}")
|
||||
self._logger.warning('Update user rejected: invalid ip=%r, user_id=%s', ip_address, user_id)
|
||||
return jsonify({'error': 'Invalid IP address'}), 400
|
||||
|
||||
success = self._user_service.update_user(
|
||||
@@ -135,21 +156,20 @@ class UserController:
|
||||
model_permissions=model_permissions,
|
||||
)
|
||||
if not success:
|
||||
self._logger.warning(f"Update user failed: user_id={user_id}")
|
||||
self._logger.warning('Update user failed: user_id=%s', user_id)
|
||||
return jsonify({'error': 'Failed to update user'}), 400
|
||||
|
||||
self._logger.info(f"Update user succeeded: user_id={user_id}")
|
||||
self._logger.info('Update user succeeded: user_id=%s', user_id)
|
||||
return jsonify({'message': 'User updated successfully'})
|
||||
except ValueError as exc:
|
||||
return jsonify({'error': str(exc)}), 400
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error updating user: {exc}')
|
||||
self._logger.error('Error updating user: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def batch_users(self) -> Response:
|
||||
"""执行用户批量操作。"""
|
||||
def batch_users(self) -> ResponseReturnValue:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
payload = self._get_request_payload()
|
||||
action = str(payload.get('action') or '').strip().lower()
|
||||
if action != 'set_model_permissions':
|
||||
raise ValueError(f'Unsupported user batch action: {action or "<empty>"}')
|
||||
@@ -169,31 +189,29 @@ class UserController:
|
||||
status_code = 404 if 'not found' in message.lower() else 400
|
||||
return jsonify({'error': message}), status_code
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error applying user batch action: {exc}')
|
||||
self._logger.error('Error applying user batch action: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def delete_user(self, user_id: int) -> Response:
|
||||
"""删除用户。"""
|
||||
def delete_user(self, user_id: int) -> ResponseReturnValue:
|
||||
try:
|
||||
if not self._user_service.delete_user(user_id):
|
||||
self._logger.warning(f"Delete user failed: user_id={user_id}")
|
||||
self._logger.warning('Delete user failed: user_id=%s', user_id)
|
||||
return jsonify({'error': 'Failed to delete user'}), 400
|
||||
self._logger.info(f"Delete user succeeded: user_id={user_id}")
|
||||
self._logger.info('Delete user succeeded: user_id=%s', user_id)
|
||||
return jsonify({'message': 'User deleted successfully'})
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error deleting user: {exc}')
|
||||
self._logger.error('Error deleting user: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def toggle_user(self, user_id: int) -> Response:
|
||||
"""切换用户白名单状态。"""
|
||||
def toggle_user(self, user_id: int) -> ResponseReturnValue:
|
||||
try:
|
||||
if not self._config_manager.is_chat_whitelist_enabled():
|
||||
return jsonify({'error': 'Whitelist control is disabled'}), 400
|
||||
if not self._user_service.toggle_user_status(user_id):
|
||||
self._logger.warning(f"Toggle user whitelist failed: user_id={user_id}")
|
||||
self._logger.warning('Toggle user whitelist failed: user_id=%s', user_id)
|
||||
return jsonify({'error': 'Failed to toggle user status'}), 400
|
||||
self._logger.info(f"Toggle user whitelist succeeded: user_id={user_id}")
|
||||
self._logger.info('Toggle user whitelist succeeded: user_id=%s', user_id)
|
||||
return jsonify({'message': 'User status toggled successfully'})
|
||||
except Exception as exc:
|
||||
self._logger.error(f'Error toggling user status: {exc}')
|
||||
self._logger.error('Error toggling user status: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
@@ -2,7 +2,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""页面与统计控制器。"""
|
||||
|
||||
from flask import Response, jsonify, render_template, request
|
||||
from flask import jsonify, render_template, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from ..application.app_context import AppContext
|
||||
from ..services import AuthenticationService, LogService
|
||||
@@ -24,27 +25,27 @@ class WebController:
|
||||
def _register_routes(self) -> None:
|
||||
auth = require_authentication(self._auth_service)
|
||||
|
||||
self._app.route("/")(auth(self.index))
|
||||
self._app.route("/users")(auth(self.users_page))
|
||||
self._app.route("/providers")(auth(self.providers_page))
|
||||
self._app.route('/')(auth(self.index))
|
||||
self._app.route('/users')(auth(self.users_page))
|
||||
self._app.route('/providers')(auth(self.providers_page))
|
||||
|
||||
self._app.route("/api/statistics", methods=["GET"])(auth(self.get_statistics))
|
||||
self._app.route("/api/request-logs", methods=["GET"])(auth(self.get_request_logs))
|
||||
self._app.route("/api/usernames", methods=["GET"])(auth(self.get_usernames))
|
||||
self._app.route("/api/request-models", methods=["GET"])(auth(self.get_request_models))
|
||||
self._app.route('/api/statistics', methods=['GET'])(auth(self.get_statistics))
|
||||
self._app.route('/api/request-logs', methods=['GET'])(auth(self.get_request_logs))
|
||||
self._app.route('/api/usernames', methods=['GET'])(auth(self.get_usernames))
|
||||
self._app.route('/api/request-models', methods=['GET'])(auth(self.get_request_models))
|
||||
|
||||
def index(self) -> str:
|
||||
return render_template(
|
||||
"index.html",
|
||||
active_page="index",
|
||||
'index.html',
|
||||
active_page='index',
|
||||
current_username=self._get_current_username(),
|
||||
auth_enabled=self._auth_service.is_auth_enabled(),
|
||||
)
|
||||
|
||||
def users_page(self) -> str:
|
||||
return render_template(
|
||||
"users.html",
|
||||
active_page="users",
|
||||
'users.html',
|
||||
active_page='users',
|
||||
chat_whitelist_enabled=self._config_manager.is_chat_whitelist_enabled(),
|
||||
current_username=self._get_current_username(),
|
||||
auth_enabled=self._auth_service.is_auth_enabled(),
|
||||
@@ -52,8 +53,8 @@ class WebController:
|
||||
|
||||
def providers_page(self) -> str:
|
||||
return render_template(
|
||||
"providers.html",
|
||||
active_page="providers",
|
||||
'providers.html',
|
||||
active_page='providers',
|
||||
chat_whitelist_enabled=self._config_manager.is_chat_whitelist_enabled(),
|
||||
current_username=self._get_current_username(),
|
||||
auth_enabled=self._auth_service.is_auth_enabled(),
|
||||
@@ -61,66 +62,66 @@ class WebController:
|
||||
|
||||
def _get_current_username(self) -> str:
|
||||
if not self._auth_service.is_auth_enabled():
|
||||
return ""
|
||||
return ''
|
||||
|
||||
session_token = request.cookies.get("session_token")
|
||||
return self._auth_service.get_session_username(session_token) or ""
|
||||
session_token = request.cookies.get('session_token')
|
||||
return self._auth_service.get_session_username(session_token) or ''
|
||||
|
||||
def get_statistics(self) -> Response:
|
||||
def get_statistics(self) -> ResponseReturnValue:
|
||||
try:
|
||||
self._logger.debug(
|
||||
"Statistics queried: start_date=%s end_date=%s username=%s request_model=%s",
|
||||
request.args.get("start_date"),
|
||||
request.args.get("end_date"),
|
||||
request.args.get("username"),
|
||||
request.args.get("request_model"),
|
||||
'Statistics queried: start_date=%s end_date=%s username=%s request_model=%s',
|
||||
request.args.get('start_date'),
|
||||
request.args.get('end_date'),
|
||||
request.args.get('username'),
|
||||
request.args.get('request_model'),
|
||||
)
|
||||
stats = self._log_service.get_statistics(
|
||||
request.args.get("start_date"),
|
||||
request.args.get("end_date"),
|
||||
request.args.get("username"),
|
||||
request.args.get("request_model"),
|
||||
request.args.get('start_date'),
|
||||
request.args.get('end_date'),
|
||||
request.args.get('username'),
|
||||
request.args.get('request_model'),
|
||||
)
|
||||
return jsonify(stats)
|
||||
except Exception as exc:
|
||||
self._logger.error(f"Error getting statistics: {exc}")
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
self._logger.error('Error getting statistics: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_request_logs(self) -> Response:
|
||||
def get_request_logs(self) -> ResponseReturnValue:
|
||||
try:
|
||||
page = max(int(request.args.get("page", 1)), 1)
|
||||
page_size = min(max(int(request.args.get("page_size", 50)), 1), 200)
|
||||
self._logger.debug(f"Request logs queried: page={page}, page_size={page_size}")
|
||||
page = max(int(request.args.get('page', 1)), 1)
|
||||
page_size = min(max(int(request.args.get('page_size', 50)), 1), 200)
|
||||
self._logger.debug('Request logs queried: page=%s, page_size=%s', page, page_size)
|
||||
|
||||
logs = self._log_service.get_request_logs(
|
||||
page,
|
||||
page_size,
|
||||
request.args.get("start_date"),
|
||||
request.args.get("end_date"),
|
||||
request.args.get("username"),
|
||||
request.args.get("request_model"),
|
||||
request.args.get('start_date'),
|
||||
request.args.get('end_date'),
|
||||
request.args.get('username'),
|
||||
request.args.get('request_model'),
|
||||
)
|
||||
return jsonify(logs)
|
||||
except ValueError:
|
||||
return jsonify({"error": "page and page_size must be integers"}), 400
|
||||
return jsonify({'error': 'page and page_size must be integers'}), 400
|
||||
except Exception as exc:
|
||||
self._logger.error(f"Error getting request logs: {exc}")
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
self._logger.error('Error getting request logs: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_usernames(self) -> Response:
|
||||
def get_usernames(self) -> ResponseReturnValue:
|
||||
try:
|
||||
usernames = self._log_service.get_unique_usernames()
|
||||
self._logger.debug(f"Usernames queried: count={len(usernames)}")
|
||||
self._logger.debug('Usernames queried: count=%s', len(usernames))
|
||||
return jsonify(usernames)
|
||||
except Exception as exc:
|
||||
self._logger.error(f"Error getting usernames: {exc}")
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
self._logger.error('Error getting usernames: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
|
||||
def get_request_models(self) -> Response:
|
||||
def get_request_models(self) -> ResponseReturnValue:
|
||||
try:
|
||||
models = self._log_service.get_unique_request_models()
|
||||
self._logger.debug(f"Request models queried: count={len(models)}")
|
||||
self._logger.debug('Request models queried: count=%s', len(models))
|
||||
return jsonify(models)
|
||||
except Exception as exc:
|
||||
self._logger.error(f"Error getting request models: {exc}")
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
self._logger.error('Error getting request models: %s', exc)
|
||||
return jsonify({'error': str(exc)}), 500
|
||||
Reference in new issue
Block a user