Spaces:
Building
Building
""" | |
Thread-Safe Configuration Provider for Flare Platform | |
""" | |
import threading | |
import os | |
import json | |
import commentjson | |
from typing import Optional, Dict, List, Any | |
from datetime import datetime | |
from pathlib import Path | |
import tempfile | |
import shutil | |
from utils.utils import get_current_timestamp, normalize_timestamp, timestamps_equal | |
from .config_models import ( | |
ServiceConfig, GlobalConfig, ProjectConfig, VersionConfig, | |
IntentConfig, APIConfig, ActivityLogEntry, ParameterConfig, | |
LLMConfiguration, GenerationConfig | |
) | |
from utils.logger import log_info, log_error, log_warning, log_debug, LogTimer | |
from utils.exceptions import ( | |
RaceConditionError, ConfigurationError, ResourceNotFoundError, | |
DuplicateResourceError, ValidationError | |
) | |
from utils.encryption_utils import encrypt, decrypt | |
class ConfigProvider: | |
"""Thread-safe singleton configuration provider""" | |
_instance: Optional[ServiceConfig] = None | |
_lock = threading.RLock() # Reentrant lock for nested calls | |
_file_lock = threading.Lock() # Separate lock for file operations | |
_CONFIG_PATH = Path(__file__).parent / "service_config.jsonc" | |
def _normalize_date(date_str: Optional[str]) -> str: | |
"""Normalize date string for comparison""" | |
if not date_str: | |
return "" | |
return date_str.replace(' ', 'T').replace('+00:00', 'Z').replace('.000Z', 'Z') | |
def get(cls) -> ServiceConfig: | |
"""Get cached configuration - thread-safe""" | |
if cls._instance is None: | |
with cls._lock: | |
# Double-checked locking pattern | |
if cls._instance is None: | |
with LogTimer("config_load"): | |
cls._instance = cls._load() | |
cls._instance.build_index() | |
log_info("Configuration loaded successfully") | |
return cls._instance | |
def reload(cls) -> ServiceConfig: | |
"""Force reload configuration from file""" | |
with cls._lock: | |
log_info("Reloading configuration...") | |
cls._instance = None | |
return cls.get() | |
def _load(cls) -> ServiceConfig: | |
"""Load configuration from file""" | |
try: | |
if not cls._CONFIG_PATH.exists(): | |
raise ConfigurationError( | |
f"Config file not found: {cls._CONFIG_PATH}", | |
config_key="service_config.jsonc" | |
) | |
with open(cls._CONFIG_PATH, 'r', encoding='utf-8') as f: | |
config_data = commentjson.load(f) | |
# Debug: İlk project'in tarihini kontrol et | |
if 'projects' in config_data and len(config_data['projects']) > 0: | |
first_project = config_data['projects'][0] | |
log_debug(f"🔍 Raw project data - last_update_date: {first_project.get('last_update_date')}") | |
# Ensure required fields | |
if 'config' not in config_data: | |
config_data['config'] = {} | |
# Ensure providers exist | |
cls._ensure_providers(config_data) | |
# Parse API configs (handle JSON strings) | |
if 'apis' in config_data: | |
cls._parse_api_configs(config_data['apis']) | |
# Validate and create model | |
cfg = ServiceConfig.model_validate(config_data) | |
# Debug: Model'e dönüştükten sonra kontrol et | |
if cfg.projects and len(cfg.projects) > 0: | |
log_debug(f"🔍 Parsed project - last_update_date: {cfg.projects[0].last_update_date}") | |
log_debug(f"🔍 Type: {type(cfg.projects[0].last_update_date)}") | |
# Log versions published status after parsing | |
for version in cfg.projects[0].versions: | |
log_debug(f"🔍 Parsed version {version.no} - published: {version.published} (type: {type(version.published)})") | |
log_debug( | |
"Configuration loaded", | |
projects=len(cfg.projects), | |
apis=len(cfg.apis), | |
users=len(cfg.global_config.users) | |
) | |
return cfg | |
except Exception as e: | |
log_error(f"Error loading config", error=str(e), path=str(cls._CONFIG_PATH)) | |
raise ConfigurationError(f"Failed to load configuration: {e}") | |
def _parse_api_configs(cls, apis: List[Dict[str, Any]]) -> None: | |
"""Parse JSON string fields in API configs""" | |
for api in apis: | |
# Parse headers | |
if 'headers' in api and isinstance(api['headers'], str): | |
try: | |
api['headers'] = json.loads(api['headers']) | |
except json.JSONDecodeError: | |
api['headers'] = {} | |
# Parse body_template | |
if 'body_template' in api and isinstance(api['body_template'], str): | |
try: | |
api['body_template'] = json.loads(api['body_template']) | |
except json.JSONDecodeError: | |
api['body_template'] = {} | |
# Parse auth configs | |
if 'auth' in api and api['auth']: | |
cls._parse_auth_config(api['auth']) | |
def _parse_auth_config(cls, auth: Dict[str, Any]) -> None: | |
"""Parse auth configuration""" | |
# Parse token_request_body | |
if 'token_request_body' in auth and isinstance(auth['token_request_body'], str): | |
try: | |
auth['token_request_body'] = json.loads(auth['token_request_body']) | |
except json.JSONDecodeError: | |
auth['token_request_body'] = {} | |
# Parse token_refresh_body | |
if 'token_refresh_body' in auth and isinstance(auth['token_refresh_body'], str): | |
try: | |
auth['token_refresh_body'] = json.loads(auth['token_refresh_body']) | |
except json.JSONDecodeError: | |
auth['token_refresh_body'] = {} | |
def save(cls, config: ServiceConfig, username: str) -> None: | |
"""Thread-safe configuration save with optimistic locking""" | |
with cls._file_lock: | |
try: | |
# Convert to dict for JSON serialization | |
config_dict = config.model_dump() | |
# Load current config for race condition check | |
try: | |
current_config = cls._load() | |
# Check for race condition | |
if config.last_update_date and current_config.last_update_date: | |
if not timestamps_equal(config.last_update_date, current_config.last_update_date): | |
raise RaceConditionError( | |
"Configuration was modified by another user", | |
current_user=username, | |
last_update_user=current_config.last_update_user, | |
last_update_date=current_config.last_update_date, | |
entity_type="configuration" | |
) | |
except ConfigurationError as e: | |
# Eğer mevcut config yüklenemiyorsa, race condition kontrolünü atla | |
log_warning(f"Could not load current config for race condition check: {e}") | |
current_config = None | |
# Update metadata | |
config.last_update_date = get_current_timestamp() | |
config.last_update_user = username | |
# Convert to JSON - Pydantic v2 kullanımı | |
data = config.model_dump(mode='json') | |
json_str = json.dumps(data, ensure_ascii=False, indent=2) | |
# Backup current file if exists | |
backup_path = None | |
if cls._CONFIG_PATH.exists(): | |
backup_path = cls._CONFIG_PATH.with_suffix('.backup') | |
shutil.copy2(str(cls._CONFIG_PATH), str(backup_path)) | |
log_debug(f"Created backup at {backup_path}") | |
try: | |
# Write to temporary file first | |
temp_path = cls._CONFIG_PATH.with_suffix('.tmp') | |
with open(temp_path, 'w', encoding='utf-8') as f: | |
f.write(json_str) | |
# Validate the temp file by trying to load it | |
with open(temp_path, 'r', encoding='utf-8') as f: | |
test_data = commentjson.load(f) | |
ServiceConfig.model_validate(test_data) | |
# If validation passes, replace the original | |
shutil.move(str(temp_path), str(cls._CONFIG_PATH)) | |
# Delete backup if save successful | |
if backup_path and backup_path.exists(): | |
backup_path.unlink() | |
except Exception as e: | |
# Restore from backup if something went wrong | |
if backup_path and backup_path.exists(): | |
shutil.move(str(backup_path), str(cls._CONFIG_PATH)) | |
log_error(f"Restored configuration from backup due to error: {e}") | |
raise | |
# Update cached instance | |
with cls._lock: | |
cls._instance = config | |
log_info( | |
"Configuration saved successfully", | |
user=username, | |
last_update=config.last_update_date | |
) | |
except Exception as e: | |
log_error(f"Failed to save config", error=str(e)) | |
raise ConfigurationError( | |
f"Failed to save configuration: {str(e)}", | |
config_key="service_config.jsonc" | |
) | |
# ===================== Environment Methods ===================== | |
def update_environment(cls, update_data: dict, username: str) -> None: | |
"""Update environment configuration""" | |
with cls._lock: | |
config = cls.get() | |
# Update providers | |
if 'llm_provider' in update_data: | |
config.global_config.llm_provider = update_data['llm_provider'] | |
if 'tts_provider' in update_data: | |
config.global_config.tts_provider = update_data['tts_provider'] | |
if 'stt_provider' in update_data: | |
config.global_config.stt_provider = update_data['stt_provider'] | |
# Log activity | |
cls._add_activity( | |
config, username, "UPDATE_ENVIRONMENT", | |
"environment", None, | |
f"Updated providers" | |
) | |
# Save | |
cls.save(config, username) | |
def _ensure_providers(cls, config_data: Dict[str, Any]) -> None: | |
"""Ensure config has required provider structure""" | |
if 'config' not in config_data: | |
config_data['config'] = {} | |
config = config_data['config'] | |
# Ensure provider settings exist | |
if 'llm_provider' not in config: | |
config['llm_provider'] = { | |
'name': 'spark_cloud', | |
'api_key': '', | |
'endpoint': 'http://localhost:8080', | |
'settings': {} | |
} | |
if 'tts_provider' not in config: | |
config['tts_provider'] = { | |
'name': 'no_tts', | |
'api_key': '', | |
'endpoint': None, | |
'settings': {} | |
} | |
if 'stt_provider' not in config: | |
config['stt_provider'] = { | |
'name': 'no_stt', | |
'api_key': '', | |
'endpoint': None, | |
'settings': {} | |
} | |
# Ensure providers list exists | |
if 'providers' not in config: | |
config['providers'] = [ | |
{ | |
"type": "llm", | |
"name": "spark_cloud", | |
"display_name": "Spark LLM (Cloud)", | |
"requires_endpoint": True, | |
"requires_api_key": True, | |
"requires_repo_info": False, | |
"description": "Spark Cloud LLM Service" | |
}, | |
{ | |
"type": "tts", | |
"name": "no_tts", | |
"display_name": "No TTS", | |
"requires_endpoint": False, | |
"requires_api_key": False, | |
"requires_repo_info": False, | |
"description": "Text-to-Speech disabled" | |
}, | |
{ | |
"type": "stt", | |
"name": "no_stt", | |
"display_name": "No STT", | |
"requires_endpoint": False, | |
"requires_api_key": False, | |
"requires_repo_info": False, | |
"description": "Speech-to-Text disabled" | |
} | |
] | |
# ===================== Project Methods ===================== | |
def get_project(cls, project_id: int) -> Optional[ProjectConfig]: | |
"""Get project by ID""" | |
config = cls.get() | |
return next((p for p in config.projects if p.id == project_id), None) | |
def create_project(cls, project_data: dict, username: str) -> ProjectConfig: | |
"""Create new project with initial version""" | |
with cls._lock: | |
config = cls.get() | |
# Check for duplicate name | |
existing_project = next((p for p in config.projects if p.name == project_data['name'] and not p.deleted), None) | |
if existing_project: | |
raise DuplicateResourceError("Project", project_data['name']) | |
# Create project | |
project = ProjectConfig( | |
id=config.project_id_counter, | |
created_date=get_current_timestamp(), | |
created_by=username, | |
version_id_counter=1, # Başlangıç değeri | |
versions=[], # Boş başla | |
**project_data | |
) | |
# Create initial version with proper models | |
initial_version = VersionConfig( | |
no=1, | |
caption="Initial version", | |
description="Auto-generated initial version", | |
published=False, # Explicitly set to False | |
deleted=False, | |
general_prompt="You are a helpful assistant.", | |
welcome_prompt=None, | |
llm=LLMConfiguration( | |
repo_id="ytu-ce-cosmos/Turkish-Llama-8b-Instruct-v0.1", | |
generation_config=GenerationConfig( | |
max_new_tokens=512, | |
temperature=0.7, | |
top_p=0.9, | |
repetition_penalty=1.1, | |
do_sample=True | |
), | |
use_fine_tune=False, | |
fine_tune_zip="" | |
), | |
intents=[], | |
created_date=get_current_timestamp(), | |
created_by=username, | |
last_update_date=None, | |
last_update_user=None, | |
publish_date=None, | |
published_by=None | |
) | |
# Add initial version to project | |
project.versions.append(initial_version) | |
project.version_id_counter = 2 # Next version will be 2 | |
# Update config | |
config.projects.append(project) | |
config.project_id_counter += 1 | |
# Log activity | |
cls._add_activity( | |
config, username, "CREATE_PROJECT", | |
"project", project.name, | |
f"Created with initial version" | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Project created with initial version", | |
project_id=project.id, | |
name=project.name, | |
user=username | |
) | |
return project | |
def update_project(cls, project_id: int, update_data: dict, username: str, expected_last_update: Optional[str] = None) -> ProjectConfig: | |
"""Update project with optimistic locking""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
# Check race condition | |
if expected_last_update is not None and expected_last_update != '': | |
if project.last_update_date and not timestamps_equal(expected_last_update, project.last_update_date): | |
raise RaceConditionError( | |
f"Project '{project.name}' was modified by another user", | |
current_user=username, | |
last_update_user=project.last_update_user, | |
last_update_date=project.last_update_date, | |
entity_type="project", | |
entity_id=project_id | |
) | |
# Update fields | |
for key, value in update_data.items(): | |
if hasattr(project, key) and key not in ['id', 'created_date', 'created_by', 'last_update_date', 'last_update_user']: | |
setattr(project, key, value) | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
cls._add_activity( | |
config, username, "UPDATE_PROJECT", | |
"project", project.name | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Project updated", | |
project_id=project.id, | |
user=username | |
) | |
return project | |
def delete_project(cls, project_id: int, username: str) -> None: | |
"""Soft delete project""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
project.deleted = True | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
cls._add_activity( | |
config, username, "DELETE_PROJECT", | |
"project", project.name | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Project deleted", | |
project_id=project.id, | |
user=username | |
) | |
def toggle_project(cls, project_id: int, username: str) -> bool: | |
"""Toggle project enabled status""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
project.enabled = not project.enabled | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
# Log activity | |
cls._add_activity( | |
config, username, "TOGGLE_PROJECT", | |
"project", project.name, | |
f"{'Enabled' if project.enabled else 'Disabled'}" | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Project toggled", | |
project_id=project.id, | |
enabled=project.enabled, | |
user=username | |
) | |
return project.enabled | |
# ===================== Version Methods ===================== | |
def create_version(cls, project_id: int, version_data: dict, username: str) -> VersionConfig: | |
"""Create new version""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
# Handle source version copy | |
if 'source_version_no' in version_data and version_data['source_version_no']: | |
source_version = next((v for v in project.versions if v.no == version_data['source_version_no']), None) | |
if source_version: | |
# Copy from source version | |
version_dict = source_version.model_dump() | |
# Remove fields that shouldn't be copied | |
for field in ['no', 'created_date', 'created_by', 'published', 'publish_date', | |
'published_by', 'last_update_date', 'last_update_user']: | |
version_dict.pop(field, None) | |
# Override with provided data | |
version_dict['caption'] = version_data.get('caption', f"Copy of {source_version.caption}") | |
else: | |
# Source not found, create blank | |
version_dict = { | |
'caption': version_data.get('caption', 'New Version'), | |
'general_prompt': '', | |
'welcome_prompt': None, | |
'llm': { | |
'repo_id': '', | |
'generation_config': { | |
'max_new_tokens': 512, | |
'temperature': 0.7, | |
'top_p': 0.95, | |
'repetition_penalty': 1.1 | |
}, | |
'use_fine_tune': False, | |
'fine_tune_zip': '' | |
}, | |
'intents': [] | |
} | |
else: | |
# Create blank version | |
version_dict = { | |
'caption': version_data.get('caption', 'New Version'), | |
'general_prompt': '', | |
'welcome_prompt': None, | |
'llm': { | |
'repo_id': '', | |
'generation_config': { | |
'max_new_tokens': 512, | |
'temperature': 0.7, | |
'top_p': 0.95, | |
'repetition_penalty': 1.1 | |
}, | |
'use_fine_tune': False, | |
'fine_tune_zip': '' | |
}, | |
'intents': [] | |
} | |
# Create version | |
version = VersionConfig( | |
no=project.version_id_counter, | |
published=False, # New versions are always unpublished | |
deleted=False, | |
created_date=get_current_timestamp(), | |
created_by=username, | |
last_update_date=None, | |
last_update_user=None, | |
publish_date=None, | |
published_by=None, | |
**version_dict | |
) | |
# Update project | |
project.versions.append(version) | |
project.version_id_counter += 1 | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
# Log activity | |
cls._add_activity( | |
config, username, "CREATE_VERSION", | |
"version", version.no, f"{project.name} v{version.no}", | |
f"Project: {project.name}" | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Version created", | |
project_id=project.id, | |
version_no=version.no, | |
user=username | |
) | |
return version | |
def publish_version(cls, project_id: int, version_no: int, username: str) -> tuple[ProjectConfig, VersionConfig]: | |
"""Publish a version""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
version = next((v for v in project.versions if v.no == version_no), None) | |
if not version: | |
raise ResourceNotFoundError("version", version_no) | |
# Unpublish other versions | |
for v in project.versions: | |
if v.published and v.no != version_no: | |
v.published = False | |
# Publish this version | |
version.published = True | |
version.publish_date = get_current_timestamp() | |
version.published_by = username | |
# Update project | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
# Log activity | |
cls._add_activity( | |
config, username, "PUBLISH_VERSION", | |
"version", f"{project.name} v{version.no}" | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Version published", | |
project_id=project.id, | |
version_no=version.no, | |
user=username | |
) | |
return project, version | |
def update_version(cls, project_id: int, version_no: int, update_data: dict, username: str, expected_last_update: Optional[str] = None) -> VersionConfig: | |
"""Update version with optimistic locking""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
version = next((v for v in project.versions if v.no == version_no), None) | |
if not version: | |
raise ResourceNotFoundError("version", version_no) | |
# Ensure published is a boolean (safety check) | |
if version.published is None: | |
version.published = False | |
# Published versions cannot be edited | |
if version.published: | |
raise ValidationError("Published versions cannot be modified") | |
# Check race condition | |
if expected_last_update is not None and expected_last_update != '': | |
if version.last_update_date and not timestamps_equal(expected_last_update, version.last_update_date): | |
raise RaceConditionError( | |
f"Version '{version.no}' was modified by another user", | |
current_user=username, | |
last_update_user=version.last_update_user, | |
last_update_date=version.last_update_date, | |
entity_type="version", | |
entity_id=f"{project_id}:{version_no}" | |
) | |
# Update fields | |
for key, value in update_data.items(): | |
if hasattr(version, key) and key not in ['no', 'created_date', 'created_by', 'published', 'last_update_date']: | |
# Handle LLM config | |
if key == 'llm' and isinstance(value, dict): | |
setattr(version, key, LLMConfiguration(**value)) | |
# Handle intents | |
elif key == 'intents' and isinstance(value, list): | |
intents = [] | |
for intent_data in value: | |
if isinstance(intent_data, dict): | |
intents.append(IntentConfig(**intent_data)) | |
else: | |
intents.append(intent_data) | |
setattr(version, key, intents) | |
else: | |
setattr(version, key, value) | |
version.last_update_date = get_current_timestamp() | |
version.last_update_user = username | |
# Update project last update | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
# Log activity | |
cls._add_activity( | |
config, username, "UPDATE_VERSION", | |
"version", f"{project.name} v{version.no}" | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Version updated", | |
project_id=project.id, | |
version_no=version.no, | |
user=username | |
) | |
return version | |
def delete_version(cls, project_id: int, version_no: int, username: str) -> None: | |
"""Soft delete version""" | |
with cls._lock: | |
config = cls.get() | |
project = cls.get_project(project_id) | |
if not project: | |
raise ResourceNotFoundError("project", project_id) | |
version = next((v for v in project.versions if v.no == version_no), None) | |
if not version: | |
raise ResourceNotFoundError("version", version_no) | |
if version.published: | |
raise ValidationError("Cannot delete published version") | |
version.deleted = True | |
version.last_update_date = get_current_timestamp() | |
version.last_update_user = username | |
# Update project | |
project.last_update_date = get_current_timestamp() | |
project.last_update_user = username | |
# Log activity | |
cls._add_activity( | |
config, username, "DELETE_VERSION", | |
"version", f"{project.name} v{version.no}" | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"Version deleted", | |
project_id=project.id, | |
version_no=version.no, | |
user=username | |
) | |
# ===================== API Methods ===================== | |
def create_api(cls, api_data: dict, username: str) -> APIConfig: | |
"""Create new API""" | |
with cls._lock: | |
config = cls.get() | |
# Check for duplicate name | |
existing_api = next((a for a in config.apis if a.name == api_data['name'] and not a.deleted), None) | |
if existing_api: | |
raise DuplicateResourceError("API", api_data['name']) | |
# Create API | |
api = APIConfig( | |
created_date=get_current_timestamp(), | |
created_by=username, | |
**api_data | |
) | |
# Add to config | |
config.apis.append(api) | |
# Rebuild index | |
config.build_index() | |
# Log activity | |
cls._add_activity( | |
config, username, "CREATE_API", | |
"api", api.name | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"API created", | |
api_name=api.name, | |
user=username | |
) | |
return api | |
def update_api(cls, api_name: str, update_data: dict, username: str, expected_last_update: Optional[str] = None) -> APIConfig: | |
"""Update API with optimistic locking""" | |
with cls._lock: | |
config = cls.get() | |
api = config.get_api(api_name) | |
if not api: | |
raise ResourceNotFoundError("api", api_name) | |
# Check race condition | |
if expected_last_update is not None and expected_last_update != '': | |
if api.last_update_date and not timestamps_equal(expected_last_update, api.last_update_date): | |
raise RaceConditionError( | |
f"API '{api.name}' was modified by another user", | |
current_user=username, | |
last_update_user=api.last_update_user, | |
last_update_date=api.last_update_date, | |
entity_type="api", | |
entity_id=api.name | |
) | |
# Update fields | |
for key, value in update_data.items(): | |
if hasattr(api, key) and key not in ['name', 'created_date', 'created_by', 'last_update_date']: | |
setattr(api, key, value) | |
api.last_update_date = get_current_timestamp() | |
api.last_update_user = username | |
# Rebuild index | |
config.build_index() | |
# Log activity | |
cls._add_activity( | |
config, username, "UPDATE_API", | |
"api", api.name | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"API updated", | |
api_name=api.name, | |
user=username | |
) | |
return api | |
def delete_api(cls, api_name: str, username: str) -> None: | |
"""Soft delete API""" | |
with cls._lock: | |
config = cls.get() | |
api = config.get_api(api_name) | |
if not api: | |
raise ResourceNotFoundError("api", api_name) | |
api.deleted = True | |
api.last_update_date = get_current_timestamp() | |
api.last_update_user = username | |
# Rebuild index | |
config.build_index() | |
# Log activity | |
cls._add_activity( | |
config, username, "DELETE_API", | |
"api", api.name | |
) | |
# Save | |
cls.save(config, username) | |
log_info( | |
"API deleted", | |
api_name=api.name, | |
user=username | |
) | |
# ===================== Activity Methods ===================== | |
def _add_activity( | |
cls, | |
config: ServiceConfig, | |
username: str, | |
action: str, | |
entity_type: str, | |
entity_name: Optional[str] = None, | |
details: Optional[str] = None | |
) -> None: | |
"""Add activity log entry""" | |
# Activity ID'sini oluştur - mevcut en yüksek ID'yi bul | |
max_id = 0 | |
if config.activity_log: | |
max_id = max((entry.id for entry in config.activity_log if entry.id), default=0) | |
activity_id = max_id + 1 | |
activity = ActivityLogEntry( | |
id=activity_id, | |
timestamp=get_current_timestamp(), | |
username=username, | |
action=action, | |
entity_type=entity_type, | |
entity_name=entity_name, | |
details=details | |
) | |
config.activity_log.append(activity) | |
# Keep only last 1000 entries | |
if len(config.activity_log) > 1000: | |
config.activity_log = config.activity_log[-1000:] |