ciyidogan commited on
Commit
6b1b3bf
Β·
verified Β·
1 Parent(s): a262db3

Update admin_routes.py

Browse files
Files changed (1) hide show
  1. 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
- if not bcrypt.checkpw(request.current_password.encode('utf-8'), user.password_hash.encode('utf-8')):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- cfg.save()
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(