#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Provider 管理 API。""" from typing import Any from flask import jsonify, request from flask.typing import ResponseReturnValue from ..application.app_context import AppContext from ..services import ( AuthGroupService, AuthenticationService, ModelDiscoveryService, ProviderService, SettingsService, ) from .decorators import require_authentication class ProviderController: """处理 provider 配置、认证分组与相关管理 API。""" def __init__( self, ctx: AppContext, provider_service: ProviderService, auth_group_service: AuthGroupService, model_discovery_service: ModelDiscoveryService, settings_service: SettingsService, auth_service: AuthenticationService, ): self._app = ctx.flask_app self._logger = ctx.logger self._provider_service = provider_service self._auth_group_service = auth_group_service self._model_discovery_service = model_discovery_service self._settings_service = settings_service 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) self._app.route('/api/providers', methods=['GET'])(auth(self.get_providers)) self._app.route('/api/providers', methods=['POST'])(auth(self.create_provider)) self._app.route('/api/providers/batch', methods=['POST'])(auth(self.batch_providers)) self._app.route('/api/providers/fetch-models', methods=['GET'])(auth(self.fetch_models)) self._app.route('/api/providers/chat-whitelist', methods=['PUT'])(auth(self.update_chat_whitelist)) self._app.route('/api/providers/', methods=['GET'])(auth(self.get_provider)) self._app.route('/api/providers/', methods=['PUT'])(auth(self.update_provider)) self._app.route('/api/providers/', methods=['DELETE'])(auth(self.delete_provider)) self._app.route('/api/providers//disable', methods=['POST'])(auth(self.disable_provider)) self._app.route('/api/providers//enable', methods=['POST'])(auth(self.enable_provider)) self._app.route('/api/auth-groups', methods=['GET'])(auth(self.get_auth_groups)) self._app.route('/api/auth-groups', methods=['POST'])(auth(self.create_auth_group)) self._app.route('/api/auth-groups/import-entries', methods=['POST'])(auth(self.import_auth_group_entries)) self._app.route('/api/auth-groups/', methods=['GET'])(auth(self.get_auth_group)) self._app.route('/api/auth-groups/', methods=['PUT'])(auth(self.update_auth_group)) self._app.route('/api/auth-groups/', methods=['DELETE'])(auth(self.delete_auth_group)) self._app.route('/api/auth-groups//runtime', methods=['GET'])(auth(self.get_auth_group_runtime)) self._app.route('/api/auth-groups//entries//clear-cooldown', methods=['POST'])( auth(self.clear_auth_group_entry_cooldown) ) self._app.route('/api/auth-groups//entries//disable', methods=['POST'])( auth(self.disable_auth_group_entry) ) self._app.route('/api/auth-groups//entries//enable', methods=['POST'])( auth(self.enable_auth_group_entry) ) self._app.route('/api/auth-groups//entries//reset-minute-usage', methods=['POST'])( auth(self.reset_auth_group_entry_minute_usage) ) self._app.route('/api/auth-groups//entries//reset', methods=['POST'])( auth(self.reset_auth_group_entry_runtime) ) self._app.route('/api/auth-groups//entries//restore', methods=['POST'])( auth(self.restore_auth_group_entry) ) 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) -> ResponseReturnValue: try: provider = self._provider_service.get_provider(name) if provider is None: return jsonify({'error': 'Provider not found'}), 404 return jsonify(provider) except Exception as exc: self._logger.error('Error getting provider: %s', exc) return jsonify({'error': str(exc)}), 500 def create_provider(self) -> ResponseReturnValue: try: 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 except ValueError as exc: return jsonify({'error': str(exc)}), 400 except Exception as exc: self._logger.error('Error creating provider: %s', exc) return jsonify({'error': str(exc)}), 500 def update_provider(self, name: str) -> ResponseReturnValue: try: 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) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error updating provider: %s', exc) return jsonify({'error': str(exc)}), 500 def delete_provider(self, name: str) -> ResponseReturnValue: try: self._provider_service.delete_provider(name) self._logger.info('Provider deleted: %s', name) return jsonify({'message': 'Provider deleted successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error deleting provider: %s', exc) return jsonify({'error': str(exc)}), 500 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) return jsonify(provider) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error disabling provider: %s', exc) return jsonify({'error': str(exc)}), 500 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) return jsonify(provider) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error enabling provider: %s', exc) return jsonify({'error': str(exc)}), 500 def batch_providers(self) -> ResponseReturnValue: try: payload = self._get_request_payload() action = str(payload.get('action') or '').strip().lower() 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': result = self._provider_service.batch_set_provider_enabled(names, enabled=False) elif action == 'delete': result = self._provider_service.batch_delete_providers(names) else: raise ValueError(f'Unsupported provider batch action: {action or ""}') self._logger.info('Provider batch action completed: action=%s count=%s', action, result.get('count', 0)) return jsonify(result) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error applying provider batch action: %s', exc) return jsonify({'error': str(exc)}), 500 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) -> ResponseReturnValue: try: auth_group = self._auth_group_service.get_auth_group(name) if auth_group is None: return jsonify({'error': 'Auth group not found'}), 404 return jsonify(auth_group) except Exception as exc: self._logger.error('Error getting auth group: %s', exc) return jsonify({'error': str(exc)}), 500 def create_auth_group(self) -> ResponseReturnValue: try: 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 except ValueError as exc: return jsonify({'error': str(exc)}), 400 except Exception as exc: self._logger.error('Error creating auth group: %s', exc) return jsonify({'error': str(exc)}), 500 def import_auth_group_entries(self) -> ResponseReturnValue: try: 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 except Exception as exc: self._logger.error('Error importing auth group entries: %s', exc) return jsonify({'error': str(exc)}), 500 def update_auth_group(self, name: str) -> ResponseReturnValue: try: 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) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error updating auth group: %s', exc) return jsonify({'error': str(exc)}), 500 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) return jsonify({'message': 'Auth group deleted successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error deleting auth group: %s', exc) return jsonify({'error': str(exc)}), 500 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: return jsonify({'error': str(exc)}), 404 except Exception as exc: 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) -> 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) return jsonify({'message': 'Auth entry cooldown cleared successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: 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) -> 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) return jsonify({'message': 'Auth entry disabled successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: 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) -> 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) return jsonify({'message': 'Auth entry enabled successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: 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) -> 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) return jsonify({'message': 'Auth entry minute usage reset successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: 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) -> 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) return jsonify({'message': 'Auth entry runtime reset successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: 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) -> ResponseReturnValue: try: self._auth_group_service.restore_entry(name, entry_id) self._logger.info('Auth group entry restored: %s/%s', name, entry_id) return jsonify({'message': 'Auth entry restored successfully'}) except ValueError as exc: message = str(exc) status_code = 404 if 'not found' in message.lower() else 400 return jsonify({'error': message}), status_code except Exception as exc: self._logger.error('Error restoring auth group entry: %s', exc) return jsonify({'error': str(exc)}), 500 def fetch_models(self) -> ResponseReturnValue: try: result = self._model_discovery_service.fetch_models_preview( api=request.args.get('api', ''), api_key=request.args.get('api_key'), proxy=request.args.get('proxy'), timeout_seconds=request.args.get('timeout_seconds'), verify_ssl=request.args.get('verify_ssl'), ) self._logger.info( 'Provider models preview fetched: api=%s fetched=%s', request.args.get('api', ''), len(result['fetched_models']), ) return jsonify(result) except ValueError as exc: return jsonify({'error': str(exc)}), 400 except Exception as exc: self._logger.error('Error fetching provider models: %s', exc) return jsonify({'error': str(exc)}), 500 def update_chat_whitelist(self) -> ResponseReturnValue: try: 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}) except ValueError as exc: return jsonify({'error': str(exc)}), 400 except Exception as exc: self._logger.error('Error updating chat whitelist: %s', exc) return jsonify({'error': str(exc)}), 500