Spaces:
Paused
Paused
Update admin_routes.py
Browse files- admin_routes.py +152 -6
admin_routes.py
CHANGED
|
@@ -15,13 +15,14 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
| 15 |
from pydantic import BaseModel, Field
|
| 16 |
import httpx
|
| 17 |
|
| 18 |
-
from utils import verify_token, create_token
|
| 19 |
from config_provider import ConfigProvider
|
| 20 |
from logger import log_info, log_error, log_warning, log_debug
|
| 21 |
from exceptions import (
|
| 22 |
RaceConditionError, ValidationError, ResourceNotFoundError,
|
| 23 |
AuthenticationError, AuthorizationError, DuplicateResourceError
|
| 24 |
)
|
|
|
|
| 25 |
|
| 26 |
# ===================== Constants & Config =====================
|
| 27 |
security = HTTPBearer()
|
|
@@ -170,11 +171,25 @@ async def change_password(
|
|
| 170 |
if not user:
|
| 171 |
raise HTTPException(status_code=404, detail="User not found")
|
| 172 |
|
| 173 |
-
# Verify current password
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
raise HTTPException(status_code=401, detail="Current password is incorrect")
|
| 176 |
|
| 177 |
-
# Generate new password hash
|
| 178 |
salt = bcrypt.gensalt()
|
| 179 |
new_hash = bcrypt.hashpw(request.new_password.encode('utf-8'), salt)
|
| 180 |
|
|
@@ -182,8 +197,8 @@ async def change_password(
|
|
| 182 |
user.password_hash = new_hash.decode('utf-8')
|
| 183 |
user.salt = salt.decode('utf-8')
|
| 184 |
|
| 185 |
-
# Save configuration
|
| 186 |
-
|
| 187 |
|
| 188 |
log_info(f"β
Password changed for user '{username}'")
|
| 189 |
return {"success": True}
|
|
@@ -520,6 +535,137 @@ async def toggle_project(project_id: int, username: str = Depends(verify_token))
|
|
| 520 |
log_info(f"β
Project {'enabled' if enabled else 'disabled'} by {username}")
|
| 521 |
return {"enabled": enabled}
|
| 522 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
# ===================== Version Endpoints =====================
|
| 524 |
@router.get("/projects/{project_id}/versions")
|
| 525 |
async def list_versions(
|
|
|
|
| 15 |
from pydantic import BaseModel, Field
|
| 16 |
import httpx
|
| 17 |
|
| 18 |
+
from utils import verify_token, create_token, get_current_timestamp
|
| 19 |
from config_provider import ConfigProvider
|
| 20 |
from logger import log_info, log_error, log_warning, log_debug
|
| 21 |
from exceptions import (
|
| 22 |
RaceConditionError, ValidationError, ResourceNotFoundError,
|
| 23 |
AuthenticationError, AuthorizationError, DuplicateResourceError
|
| 24 |
)
|
| 25 |
+
from config_models import VersionConfig, IntentConfig, LLMConfiguration
|
| 26 |
|
| 27 |
# ===================== Constants & Config =====================
|
| 28 |
security = HTTPBearer()
|
|
|
|
| 171 |
if not user:
|
| 172 |
raise HTTPException(status_code=404, detail="User not found")
|
| 173 |
|
| 174 |
+
# Verify current password - Try both bcrypt and SHA256 for backward compatibility
|
| 175 |
+
password_valid = False
|
| 176 |
+
|
| 177 |
+
# First try bcrypt (new format)
|
| 178 |
+
try:
|
| 179 |
+
if user.password_hash.startswith("$2b$") or user.password_hash.startswith("$2a$"):
|
| 180 |
+
password_valid = bcrypt.checkpw(request.current_password.encode('utf-8'), user.password_hash.encode('utf-8'))
|
| 181 |
+
except:
|
| 182 |
+
pass
|
| 183 |
+
|
| 184 |
+
# If not valid, try SHA256 (old format)
|
| 185 |
+
if not password_valid:
|
| 186 |
+
sha256_hash = hashlib.sha256(request.current_password.encode('utf-8')).hexdigest()
|
| 187 |
+
password_valid = (user.password_hash == sha256_hash)
|
| 188 |
+
|
| 189 |
+
if not password_valid:
|
| 190 |
raise HTTPException(status_code=401, detail="Current password is incorrect")
|
| 191 |
|
| 192 |
+
# Generate new password hash (always use bcrypt for new passwords)
|
| 193 |
salt = bcrypt.gensalt()
|
| 194 |
new_hash = bcrypt.hashpw(request.new_password.encode('utf-8'), salt)
|
| 195 |
|
|
|
|
| 197 |
user.password_hash = new_hash.decode('utf-8')
|
| 198 |
user.salt = salt.decode('utf-8')
|
| 199 |
|
| 200 |
+
# Save configuration via ConfigProvider
|
| 201 |
+
ConfigProvider.save(cfg, username)
|
| 202 |
|
| 203 |
log_info(f"β
Password changed for user '{username}'")
|
| 204 |
return {"success": True}
|
|
|
|
| 535 |
log_info(f"β
Project {'enabled' if enabled else 'disabled'} by {username}")
|
| 536 |
return {"enabled": enabled}
|
| 537 |
|
| 538 |
+
# ===================== Import/Export Endpoints =====================
|
| 539 |
+
@router.get("/projects/{project_id}/export")
|
| 540 |
+
async def export_project(
|
| 541 |
+
project_id: int,
|
| 542 |
+
username: str = Depends(verify_token)
|
| 543 |
+
):
|
| 544 |
+
"""Export project as JSON"""
|
| 545 |
+
try:
|
| 546 |
+
project = ConfigProvider.get_project(project_id)
|
| 547 |
+
if not project:
|
| 548 |
+
raise HTTPException(status_code=404, detail="Project not found")
|
| 549 |
+
|
| 550 |
+
# Prepare export data
|
| 551 |
+
export_data = {
|
| 552 |
+
"name": project.name,
|
| 553 |
+
"caption": project.caption,
|
| 554 |
+
"icon": project.icon,
|
| 555 |
+
"description": project.description,
|
| 556 |
+
"default_locale": project.default_locale,
|
| 557 |
+
"supported_locales": project.supported_locales,
|
| 558 |
+
"timezone": project.timezone,
|
| 559 |
+
"region": project.region,
|
| 560 |
+
"versions": []
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
# Add versions (only non-deleted)
|
| 564 |
+
for version in project.versions:
|
| 565 |
+
if not getattr(version, 'deleted', False):
|
| 566 |
+
version_data = {
|
| 567 |
+
"caption": version.caption,
|
| 568 |
+
"description": getattr(version, 'description', ''),
|
| 569 |
+
"general_prompt": version.general_prompt,
|
| 570 |
+
"welcome_prompt": getattr(version, 'welcome_prompt', None),
|
| 571 |
+
"llm": version.llm.model_dump() if version.llm else {},
|
| 572 |
+
"intents": [intent.model_dump() for intent in version.intents]
|
| 573 |
+
}
|
| 574 |
+
export_data["versions"].append(version_data)
|
| 575 |
+
|
| 576 |
+
log_info(f"β
Project '{project.name}' exported by {username}")
|
| 577 |
+
|
| 578 |
+
return export_data
|
| 579 |
+
|
| 580 |
+
except Exception as e:
|
| 581 |
+
log_error(f"β Error exporting project {project_id}", e)
|
| 582 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 583 |
+
|
| 584 |
+
@router.post("/projects/import")
|
| 585 |
+
async def import_project(
|
| 586 |
+
project_data: dict = Body(...),
|
| 587 |
+
username: str = Depends(verify_token)
|
| 588 |
+
):
|
| 589 |
+
"""Import project from JSON"""
|
| 590 |
+
try:
|
| 591 |
+
# Validate required fields
|
| 592 |
+
if not project_data.get('name'):
|
| 593 |
+
raise HTTPException(status_code=400, detail="Project name is required")
|
| 594 |
+
|
| 595 |
+
# Check for duplicate name
|
| 596 |
+
cfg = ConfigProvider.get()
|
| 597 |
+
if any(p.name == project_data['name'] for p in cfg.projects if not p.deleted):
|
| 598 |
+
# Generate unique name
|
| 599 |
+
base_name = project_data['name']
|
| 600 |
+
counter = 1
|
| 601 |
+
while any(p.name == f"{base_name}_{counter}" for p in cfg.projects if not p.deleted):
|
| 602 |
+
counter += 1
|
| 603 |
+
project_data['name'] = f"{base_name}_{counter}"
|
| 604 |
+
log_info(f"π Project name changed to '{project_data['name']}' to avoid duplicate")
|
| 605 |
+
|
| 606 |
+
# Create project
|
| 607 |
+
new_project_data = {
|
| 608 |
+
"name": project_data['name'],
|
| 609 |
+
"caption": project_data.get('caption', project_data['name']),
|
| 610 |
+
"icon": project_data.get('icon', 'folder'),
|
| 611 |
+
"description": project_data.get('description', ''),
|
| 612 |
+
"default_locale": project_data.get('default_locale', 'tr'),
|
| 613 |
+
"supported_locales": project_data.get('supported_locales', ['tr']),
|
| 614 |
+
"timezone": project_data.get('timezone', 'Europe/Istanbul'),
|
| 615 |
+
"region": project_data.get('region', 'tr-TR')
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
# Create project
|
| 619 |
+
new_project = ConfigProvider.create_project(new_project_data, username)
|
| 620 |
+
|
| 621 |
+
# Import versions
|
| 622 |
+
if 'versions' in project_data and project_data['versions']:
|
| 623 |
+
# Remove the initial version that was auto-created
|
| 624 |
+
if new_project.versions:
|
| 625 |
+
new_project.versions.clear()
|
| 626 |
+
|
| 627 |
+
# Add imported versions
|
| 628 |
+
for idx, version_data in enumerate(project_data['versions']):
|
| 629 |
+
version = VersionConfig(
|
| 630 |
+
no=idx + 1,
|
| 631 |
+
caption=version_data.get('caption', f'Version {idx + 1}'),
|
| 632 |
+
description=version_data.get('description', ''),
|
| 633 |
+
published=False, # Imported versions are unpublished
|
| 634 |
+
deleted=False,
|
| 635 |
+
general_prompt=version_data.get('general_prompt', ''),
|
| 636 |
+
welcome_prompt=version_data.get('welcome_prompt'),
|
| 637 |
+
llm=LLMConfiguration(**version_data.get('llm', {
|
| 638 |
+
'repo_id': '',
|
| 639 |
+
'generation_config': {
|
| 640 |
+
'max_new_tokens': 512,
|
| 641 |
+
'temperature': 0.7,
|
| 642 |
+
'top_p': 0.9
|
| 643 |
+
},
|
| 644 |
+
'use_fine_tune': False,
|
| 645 |
+
'fine_tune_zip': ''
|
| 646 |
+
})),
|
| 647 |
+
intents=[IntentConfig(**intent) for intent in version_data.get('intents', [])],
|
| 648 |
+
created_date=get_current_timestamp(),
|
| 649 |
+
created_by=username
|
| 650 |
+
)
|
| 651 |
+
new_project.versions.append(version)
|
| 652 |
+
|
| 653 |
+
# Update version counter
|
| 654 |
+
new_project.version_id_counter = len(new_project.versions) + 1
|
| 655 |
+
|
| 656 |
+
# Save updated project
|
| 657 |
+
ConfigProvider.save(cfg, username)
|
| 658 |
+
|
| 659 |
+
log_info(f"β
Project '{new_project.name}' imported by {username}")
|
| 660 |
+
|
| 661 |
+
return {"success": True, "project_id": new_project.id, "project_name": new_project.name}
|
| 662 |
+
|
| 663 |
+
except DuplicateResourceError as e:
|
| 664 |
+
raise HTTPException(status_code=409, detail=str(e))
|
| 665 |
+
except Exception as e:
|
| 666 |
+
log_error(f"β Error importing project", e)
|
| 667 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 668 |
+
|
| 669 |
# ===================== Version Endpoints =====================
|
| 670 |
@router.get("/projects/{project_id}/versions")
|
| 671 |
async def list_versions(
|