Spaces:
Building
Building
Update admin_routes.py
Browse files- admin_routes.py +129 -27
admin_routes.py
CHANGED
@@ -453,24 +453,35 @@ async def update_project(
|
|
453 |
update: ProjectUpdate,
|
454 |
username: str = Depends(verify_token)
|
455 |
):
|
456 |
-
"""Update project"""
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
464 |
raise HTTPException(
|
465 |
-
status_code=
|
466 |
-
detail=
|
|
|
|
|
|
|
|
|
|
|
467 |
)
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
return updated_project.model_dump()
|
474 |
|
475 |
@router.delete("/projects/{project_id}")
|
476 |
async def delete_project(project_id: int, username: str = Depends(verify_token)):
|
@@ -525,14 +536,54 @@ async def update_version(
|
|
525 |
project_id: int,
|
526 |
version_id: int,
|
527 |
update: VersionUpdate,
|
|
|
528 |
username: str = Depends(verify_token)
|
529 |
):
|
530 |
-
"""Update version"""
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
536 |
@router.post("/projects/{project_id}/versions/{version_id}/publish")
|
537 |
async def publish_version(
|
538 |
project_id: int,
|
@@ -691,11 +742,34 @@ async def update_api(
|
|
691 |
update: APIUpdate,
|
692 |
username: str = Depends(verify_token)
|
693 |
):
|
694 |
-
"""Update API"""
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
699 |
|
700 |
@router.delete("/apis/{api_name}")
|
701 |
async def delete_api(api_name: str, username: str = Depends(verify_token)):
|
@@ -901,6 +975,34 @@ async def notify_llm_startup(project, version):
|
|
901 |
log_error("❌ Error notifying LLM provider", e)
|
902 |
raise
|
903 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
904 |
# ===================== Cleanup Task =====================
|
905 |
def cleanup_activity_log():
|
906 |
"""Cleanup old activity log entries"""
|
|
|
453 |
update: ProjectUpdate,
|
454 |
username: str = Depends(verify_token)
|
455 |
):
|
456 |
+
"""Update existing project with race condition handling"""
|
457 |
+
try:
|
458 |
+
# Optimistic locking kontrolü
|
459 |
+
result = ConfigProvider.update_project(
|
460 |
+
project_id,
|
461 |
+
update.model_dump(),
|
462 |
+
username,
|
463 |
+
expected_last_update=update.last_update_date
|
464 |
+
)
|
465 |
+
|
466 |
+
log_info(f"✅ Project {project_id} updated by {username}")
|
467 |
+
return result
|
468 |
+
|
469 |
+
except RaceConditionError as e:
|
470 |
+
log_warning(f"⚠️ Race condition detected for project {project_id}")
|
471 |
raise HTTPException(
|
472 |
+
status_code=409,
|
473 |
+
detail={
|
474 |
+
"message": e.message,
|
475 |
+
"last_update_user": e.last_update_user,
|
476 |
+
"last_update_date": e.last_update_date,
|
477 |
+
"type": "race_condition"
|
478 |
+
}
|
479 |
)
|
480 |
+
except ResourceNotFoundError:
|
481 |
+
raise HTTPException(status_code=404, detail="Project not found")
|
482 |
+
except Exception as e:
|
483 |
+
log_error(f"❌ Error updating project {project_id}", e)
|
484 |
+
raise HTTPException(status_code=500, detail=str(e))
|
|
|
485 |
|
486 |
@router.delete("/projects/{project_id}")
|
487 |
async def delete_project(project_id: int, username: str = Depends(verify_token)):
|
|
|
536 |
project_id: int,
|
537 |
version_id: int,
|
538 |
update: VersionUpdate,
|
539 |
+
force: bool = Query(default=False, description="Force update despite conflicts"),
|
540 |
username: str = Depends(verify_token)
|
541 |
):
|
542 |
+
"""Update version with race condition handling"""
|
543 |
+
try:
|
544 |
+
# Force parametresi kontrolü
|
545 |
+
if force:
|
546 |
+
log_warning(f"⚠️ Force update requested for version {version_id} by {username}")
|
547 |
+
|
548 |
+
result = ConfigProvider.update_version(
|
549 |
+
project_id,
|
550 |
+
version_id,
|
551 |
+
update.model_dump(),
|
552 |
+
username,
|
553 |
+
expected_last_update=update.last_update_date if not force else None
|
554 |
+
)
|
555 |
+
|
556 |
+
log_info(f"✅ Version {version_id} updated by {username}")
|
557 |
+
return result
|
558 |
+
|
559 |
+
except RaceConditionError as e:
|
560 |
+
if force:
|
561 |
+
# Force modunda race condition'ı yoksay
|
562 |
+
result = ConfigProvider.update_version(
|
563 |
+
project_id,
|
564 |
+
version_id,
|
565 |
+
update.model_dump(),
|
566 |
+
username,
|
567 |
+
expected_last_update=None
|
568 |
+
)
|
569 |
+
return result
|
570 |
+
else:
|
571 |
+
log_warning(f"⚠️ Race condition detected for version {version_id}")
|
572 |
+
raise HTTPException(
|
573 |
+
status_code=409,
|
574 |
+
detail={
|
575 |
+
"message": e.message,
|
576 |
+
"last_update_user": e.last_update_user,
|
577 |
+
"last_update_date": e.last_update_date,
|
578 |
+
"type": "race_condition"
|
579 |
+
}
|
580 |
+
)
|
581 |
+
except ResourceNotFoundError:
|
582 |
+
raise HTTPException(status_code=404, detail="Version not found")
|
583 |
+
except Exception as e:
|
584 |
+
log_error(f"❌ Error updating version {version_id}", e)
|
585 |
+
raise HTTPException(status_code=500, detail=str(e))
|
586 |
+
|
587 |
@router.post("/projects/{project_id}/versions/{version_id}/publish")
|
588 |
async def publish_version(
|
589 |
project_id: int,
|
|
|
742 |
update: APIUpdate,
|
743 |
username: str = Depends(verify_token)
|
744 |
):
|
745 |
+
"""Update API configuration with race condition handling"""
|
746 |
+
try:
|
747 |
+
result = ConfigProvider.update_api(
|
748 |
+
api_name,
|
749 |
+
update.model_dump(),
|
750 |
+
username,
|
751 |
+
expected_last_update=update.last_update_date
|
752 |
+
)
|
753 |
+
|
754 |
+
log_info(f"✅ API '{api_name}' updated by {username}")
|
755 |
+
return result
|
756 |
+
|
757 |
+
except RaceConditionError as e:
|
758 |
+
log_warning(f"⚠️ Race condition detected for API '{api_name}'")
|
759 |
+
raise HTTPException(
|
760 |
+
status_code=409,
|
761 |
+
detail={
|
762 |
+
"message": e.message,
|
763 |
+
"last_update_user": e.last_update_user,
|
764 |
+
"last_update_date": e.last_update_date,
|
765 |
+
"type": "race_condition"
|
766 |
+
}
|
767 |
+
)
|
768 |
+
except ResourceNotFoundError:
|
769 |
+
raise HTTPException(status_code=404, detail="API not found")
|
770 |
+
except Exception as e:
|
771 |
+
log_error(f"❌ Error updating API '{api_name}'", e)
|
772 |
+
raise HTTPException(status_code=500, detail=str(e))
|
773 |
|
774 |
@router.delete("/apis/{api_name}")
|
775 |
async def delete_api(api_name: str, username: str = Depends(verify_token)):
|
|
|
975 |
log_error("❌ Error notifying LLM provider", e)
|
976 |
raise
|
977 |
|
978 |
+
def create_token(username: str) -> str:
|
979 |
+
"""Create JWT token with secure random"""
|
980 |
+
import secrets
|
981 |
+
|
982 |
+
cfg = ConfigProvider.get()
|
983 |
+
|
984 |
+
# Token için secure random jti (JWT ID) ekle
|
985 |
+
jti = secrets.token_urlsafe(16)
|
986 |
+
|
987 |
+
payload = {
|
988 |
+
"sub": username,
|
989 |
+
"exp": datetime.now(timezone.utc) + timedelta(hours=24),
|
990 |
+
"iat": datetime.now(timezone.utc),
|
991 |
+
"jti": jti # Unique token ID
|
992 |
+
}
|
993 |
+
|
994 |
+
# Store token ID for revocation if needed
|
995 |
+
if not hasattr(cfg, '_active_tokens'):
|
996 |
+
cfg._active_tokens = set()
|
997 |
+
cfg._active_tokens.add(jti)
|
998 |
+
|
999 |
+
secret = os.environ.get("JWT_SECRET", "your-secret-key")
|
1000 |
+
token = jwt.encode(payload, secret, algorithm="HS256")
|
1001 |
+
|
1002 |
+
if isinstance(token, bytes):
|
1003 |
+
token = token.decode()
|
1004 |
+
|
1005 |
+
return token
|
1006 |
# ===================== Cleanup Task =====================
|
1007 |
def cleanup_activity_log():
|
1008 |
"""Cleanup old activity log entries"""
|