Spaces:
Running
Running
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(
|