Major WHOOSH system refactoring and feature enhancements
- Migrated from HIVE branding to WHOOSH across all components - Enhanced backend API with new services: AI models, BZZZ integration, templates, members - Added comprehensive testing suite with security, performance, and integration tests - Improved frontend with new components for project setup, AI models, and team management - Updated MCP server implementation with WHOOSH-specific tools and resources - Enhanced deployment configurations with production-ready Docker setups - Added comprehensive documentation and setup guides - Implemented age encryption service and UCXL integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
598
backend/app/api/project_setup.py
Normal file
598
backend/app/api/project_setup.py
Normal file
@@ -0,0 +1,598 @@
|
||||
"""
|
||||
Project Setup API for WHOOSH - Comprehensive project creation with GITEA integration.
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Optional, Any
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
from app.services.gitea_service import GiteaService
|
||||
from app.services.project_service import ProjectService
|
||||
from app.services.age_service import AgeService
|
||||
from app.services.member_service import MemberService
|
||||
from app.models.project import Project
|
||||
|
||||
router = APIRouter(prefix="/api/project-setup", tags=["project-setup"])
|
||||
|
||||
# Pydantic models for request/response validation
|
||||
|
||||
class ProjectTemplateConfig(BaseModel):
|
||||
template_id: str
|
||||
name: str
|
||||
description: str
|
||||
icon: str
|
||||
features: List[str]
|
||||
starter_files: Dict[str, Any] = {}
|
||||
|
||||
class AgeKeyConfig(BaseModel):
|
||||
generate_new_key: bool = True
|
||||
master_key_passphrase: Optional[str] = None
|
||||
key_backup_location: Optional[str] = None
|
||||
key_recovery_questions: Optional[List[Dict[str, str]]] = None
|
||||
|
||||
class GitConfig(BaseModel):
|
||||
repo_type: str = Field(..., pattern="^(new|existing|import)$")
|
||||
repo_name: Optional[str] = None
|
||||
git_url: Optional[str] = None
|
||||
git_owner: Optional[str] = None
|
||||
git_branch: str = "main"
|
||||
auto_initialize: bool = True
|
||||
add_gitignore: bool = True
|
||||
add_readme: bool = True
|
||||
license_type: Optional[str] = "MIT"
|
||||
private: bool = False
|
||||
|
||||
class ProjectMember(BaseModel):
|
||||
email: str
|
||||
role: str = Field(..., pattern="^(owner|maintainer|developer|viewer)$")
|
||||
age_public_key: Optional[str] = None
|
||||
invite_message: Optional[str] = None
|
||||
|
||||
class MemberConfig(BaseModel):
|
||||
initial_members: List[ProjectMember] = []
|
||||
role_permissions: Dict[str, List[str]] = {
|
||||
"owner": ["all"],
|
||||
"maintainer": ["read", "write", "deploy"],
|
||||
"developer": ["read", "write"],
|
||||
"viewer": ["read"]
|
||||
}
|
||||
|
||||
class BzzzSyncPreferences(BaseModel):
|
||||
real_time: bool = True
|
||||
conflict_resolution: str = Field("manual", pattern="^(manual|automatic|priority)$")
|
||||
backup_frequency: str = Field("hourly", pattern="^(real-time|hourly|daily)$")
|
||||
|
||||
class BzzzConfig(BaseModel):
|
||||
enable_bzzz: bool = False
|
||||
network_peers: Optional[List[str]] = None
|
||||
auto_discovery: bool = True
|
||||
task_coordination: bool = True
|
||||
ai_agent_access: bool = False
|
||||
sync_preferences: BzzzSyncPreferences = BzzzSyncPreferences()
|
||||
|
||||
class AdvancedConfig(BaseModel):
|
||||
project_visibility: str = Field("private", pattern="^(private|internal|public)$")
|
||||
security_level: str = Field("standard", pattern="^(standard|high|maximum)$")
|
||||
backup_enabled: bool = True
|
||||
monitoring_enabled: bool = True
|
||||
ci_cd_enabled: bool = False
|
||||
custom_workflows: Optional[List[str]] = None
|
||||
|
||||
class ProjectSetupRequest(BaseModel):
|
||||
# Basic Information
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: Optional[str] = Field(None, max_length=500)
|
||||
tags: Optional[List[str]] = None
|
||||
template_id: Optional[str] = None
|
||||
|
||||
# Configuration sections
|
||||
age_config: AgeKeyConfig = AgeKeyConfig()
|
||||
git_config: GitConfig
|
||||
member_config: MemberConfig = MemberConfig()
|
||||
bzzz_config: BzzzConfig = BzzzConfig()
|
||||
advanced_config: AdvancedConfig = AdvancedConfig()
|
||||
|
||||
class ProjectSetupStatus(BaseModel):
|
||||
step: str
|
||||
status: str = Field(..., pattern="^(pending|in_progress|completed|failed)$")
|
||||
message: str
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
|
||||
class ProjectSetupResponse(BaseModel):
|
||||
project_id: str
|
||||
status: str
|
||||
progress: List[ProjectSetupStatus]
|
||||
repository: Optional[Dict[str, Any]] = None
|
||||
age_keys: Optional[Dict[str, str]] = None
|
||||
member_invitations: Optional[List[Dict[str, str]]] = None
|
||||
next_steps: List[str]
|
||||
|
||||
# Project templates configuration
|
||||
PROJECT_TEMPLATES = {
|
||||
"full-stack": ProjectTemplateConfig(
|
||||
template_id="full-stack",
|
||||
name="Full-Stack Application",
|
||||
description="Complete web application with frontend, backend, and database",
|
||||
icon="🌐",
|
||||
features=["React/Vue", "Node.js/Python", "Database", "CI/CD"],
|
||||
starter_files={
|
||||
"frontend": {"package.json": {}, "src/index.js": ""},
|
||||
"backend": {"requirements.txt": "", "app.py": ""},
|
||||
"docker-compose.yml": {},
|
||||
".github/workflows/ci.yml": {}
|
||||
}
|
||||
),
|
||||
"ai-research": ProjectTemplateConfig(
|
||||
template_id="ai-research",
|
||||
name="AI Research Project",
|
||||
description="Machine learning and AI development workspace",
|
||||
icon="🤖",
|
||||
features=["Jupyter", "Python", "GPU Support", "Data Pipeline"],
|
||||
starter_files={
|
||||
"notebooks": {},
|
||||
"src": {},
|
||||
"data": {},
|
||||
"models": {},
|
||||
"requirements.txt": "",
|
||||
"environment.yml": {}
|
||||
}
|
||||
),
|
||||
"documentation": ProjectTemplateConfig(
|
||||
template_id="documentation",
|
||||
name="Documentation Site",
|
||||
description="Technical documentation and knowledge base",
|
||||
icon="📚",
|
||||
features=["Markdown", "Static Site", "Search", "Multi-language"],
|
||||
starter_files={
|
||||
"docs": {},
|
||||
"mkdocs.yml": {},
|
||||
".readthedocs.yml": {}
|
||||
}
|
||||
),
|
||||
"mobile-app": ProjectTemplateConfig(
|
||||
template_id="mobile-app",
|
||||
name="Mobile Application",
|
||||
description="Cross-platform mobile app development",
|
||||
icon="📱",
|
||||
features=["React Native", "Flutter", "Push Notifications", "App Store"],
|
||||
starter_files={
|
||||
"src": {},
|
||||
"assets": {},
|
||||
"package.json": {},
|
||||
"app.json": {}
|
||||
}
|
||||
),
|
||||
"data-science": ProjectTemplateConfig(
|
||||
template_id="data-science",
|
||||
name="Data Science",
|
||||
description="Data analysis and visualization project",
|
||||
icon="📊",
|
||||
features=["Python", "R", "Visualization", "Reports"],
|
||||
starter_files={
|
||||
"data": {},
|
||||
"notebooks": {},
|
||||
"src": {},
|
||||
"reports": {},
|
||||
"requirements.txt": {}
|
||||
}
|
||||
),
|
||||
"empty": ProjectTemplateConfig(
|
||||
template_id="empty",
|
||||
name="Empty Project",
|
||||
description="Start from scratch with minimal setup",
|
||||
icon="📁",
|
||||
features=["Git", "Basic Structure", "README"],
|
||||
starter_files={
|
||||
"README.md": "",
|
||||
".gitignore": ""
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def get_gitea_service():
|
||||
"""Dependency injection for GITEA service."""
|
||||
return GiteaService()
|
||||
|
||||
def get_project_service():
|
||||
"""Dependency injection for project service."""
|
||||
return ProjectService()
|
||||
|
||||
def get_age_service():
|
||||
"""Dependency injection for Age service."""
|
||||
return AgeService()
|
||||
|
||||
def get_member_service():
|
||||
"""Dependency injection for Member service."""
|
||||
return MemberService()
|
||||
|
||||
@router.get("/templates")
|
||||
async def get_project_templates() -> Dict[str, Any]:
|
||||
"""Get available project templates."""
|
||||
return {
|
||||
"templates": list(PROJECT_TEMPLATES.values()),
|
||||
"count": len(PROJECT_TEMPLATES)
|
||||
}
|
||||
|
||||
@router.get("/templates/{template_id}")
|
||||
async def get_project_template(template_id: str) -> ProjectTemplateConfig:
|
||||
"""Get specific project template details."""
|
||||
if template_id not in PROJECT_TEMPLATES:
|
||||
raise HTTPException(status_code=404, detail="Template not found")
|
||||
|
||||
return PROJECT_TEMPLATES[template_id]
|
||||
|
||||
@router.post("/validate-repository")
|
||||
async def validate_repository(
|
||||
owner: str,
|
||||
repo_name: str,
|
||||
gitea_service: GiteaService = Depends(get_gitea_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate repository access and BZZZ readiness."""
|
||||
return gitea_service.validate_repository_access(owner, repo_name)
|
||||
|
||||
@router.post("/create")
|
||||
async def create_project(
|
||||
request: ProjectSetupRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
gitea_service: GiteaService = Depends(get_gitea_service),
|
||||
project_service: ProjectService = Depends(get_project_service),
|
||||
age_service: AgeService = Depends(get_age_service),
|
||||
member_service: MemberService = Depends(get_member_service)
|
||||
) -> ProjectSetupResponse:
|
||||
"""Create a new project with comprehensive setup."""
|
||||
|
||||
project_id = request.name.lower().replace(" ", "-").replace("_", "-")
|
||||
|
||||
# Initialize setup progress tracking
|
||||
progress = [
|
||||
ProjectSetupStatus(step="validation", status="pending", message="Validating project configuration"),
|
||||
ProjectSetupStatus(step="age_keys", status="pending", message="Setting up Age master keys"),
|
||||
ProjectSetupStatus(step="git_repository", status="pending", message="Creating Git repository"),
|
||||
ProjectSetupStatus(step="bzzz_setup", status="pending", message="Configuring BZZZ integration"),
|
||||
ProjectSetupStatus(step="member_invites", status="pending", message="Sending member invitations"),
|
||||
ProjectSetupStatus(step="finalization", status="pending", message="Finalizing project setup")
|
||||
]
|
||||
|
||||
try:
|
||||
# Step 1: Validation
|
||||
progress[0].status = "in_progress"
|
||||
progress[0].message = "Validating project name and configuration"
|
||||
|
||||
# Check if project name is available
|
||||
existing_project = project_service.get_project_by_id(project_id)
|
||||
if existing_project:
|
||||
progress[0].status = "failed"
|
||||
progress[0].message = f"Project '{project_id}' already exists"
|
||||
raise HTTPException(status_code=409, detail="Project name already exists")
|
||||
|
||||
progress[0].status = "completed"
|
||||
progress[0].message = "Validation completed"
|
||||
|
||||
# Step 2: Age Keys Setup
|
||||
progress[1].status = "in_progress"
|
||||
age_keys = None
|
||||
|
||||
if request.age_config.generate_new_key:
|
||||
progress[1].message = "Generating Age master key pair"
|
||||
age_keys = await generate_age_keys(project_id, request.age_config, age_service)
|
||||
|
||||
if age_keys:
|
||||
progress[1].status = "completed"
|
||||
progress[1].message = f"Age master keys generated (Key ID: {age_keys['key_id']})"
|
||||
progress[1].details = {
|
||||
"key_id": age_keys["key_id"],
|
||||
"public_key": age_keys["public_key"],
|
||||
"encrypted": age_keys["encrypted"],
|
||||
"backup_created": age_keys.get("backup_created", False)
|
||||
}
|
||||
else:
|
||||
progress[1].status = "failed"
|
||||
progress[1].message = "Age key generation failed"
|
||||
raise HTTPException(status_code=500, detail="Age key generation failed")
|
||||
else:
|
||||
progress[1].status = "completed"
|
||||
progress[1].message = "Skipped Age key generation"
|
||||
|
||||
# Step 3: Git Repository Setup
|
||||
progress[2].status = "in_progress"
|
||||
repository_info = None
|
||||
|
||||
if request.git_config.repo_type == "new":
|
||||
progress[2].message = "Creating new Git repository"
|
||||
|
||||
# Prepare repository data
|
||||
repo_data = {
|
||||
"name": request.git_config.repo_name or project_id,
|
||||
"description": request.description or f"WHOOSH project: {request.name}",
|
||||
"owner": request.git_config.git_owner or "whoosh",
|
||||
"private": request.git_config.private
|
||||
}
|
||||
|
||||
repository_info = gitea_service.setup_project_repository(repo_data)
|
||||
|
||||
if repository_info:
|
||||
progress[2].status = "completed"
|
||||
progress[2].message = f"Repository created: {repository_info['gitea_url']}"
|
||||
progress[2].details = repository_info
|
||||
else:
|
||||
progress[2].status = "failed"
|
||||
progress[2].message = "Failed to create Git repository"
|
||||
raise HTTPException(status_code=500, detail="Repository creation failed")
|
||||
|
||||
elif request.git_config.repo_type == "existing":
|
||||
progress[2].message = "Validating existing repository"
|
||||
|
||||
validation = gitea_service.validate_repository_access(
|
||||
request.git_config.git_owner,
|
||||
request.git_config.repo_name
|
||||
)
|
||||
|
||||
if validation["accessible"]:
|
||||
repository_info = {
|
||||
"repository": validation["repository"],
|
||||
"gitea_url": f"{gitea_service.gitea_base_url}/{request.git_config.git_owner}/{request.git_config.repo_name}",
|
||||
"bzzz_enabled": validation["bzzz_ready"]
|
||||
}
|
||||
progress[2].status = "completed"
|
||||
progress[2].message = "Existing repository validated"
|
||||
else:
|
||||
progress[2].status = "failed"
|
||||
progress[2].message = f"Repository validation failed: {validation.get('error', 'Unknown error')}"
|
||||
raise HTTPException(status_code=400, detail="Repository validation failed")
|
||||
|
||||
# Step 4: BZZZ Setup
|
||||
progress[3].status = "in_progress"
|
||||
|
||||
if request.bzzz_config.enable_bzzz:
|
||||
progress[3].message = "Configuring BZZZ task coordination"
|
||||
|
||||
# Ensure BZZZ labels are set up
|
||||
if repository_info and request.git_config.repo_type == "new":
|
||||
# Labels already set up during repository creation
|
||||
pass
|
||||
elif repository_info:
|
||||
# Set up labels for existing repository
|
||||
gitea_service._setup_bzzz_labels(
|
||||
request.git_config.git_owner,
|
||||
request.git_config.repo_name
|
||||
)
|
||||
|
||||
progress[3].status = "completed"
|
||||
progress[3].message = "BZZZ integration configured"
|
||||
else:
|
||||
progress[3].status = "completed"
|
||||
progress[3].message = "BZZZ integration disabled"
|
||||
|
||||
# Step 5: Member Invitations
|
||||
progress[4].status = "in_progress"
|
||||
member_invitations = []
|
||||
|
||||
if request.member_config.initial_members:
|
||||
progress[4].message = f"Sending invitations to {len(request.member_config.initial_members)} members"
|
||||
|
||||
# Get Age public key for invitations
|
||||
age_public_key = None
|
||||
if age_keys:
|
||||
age_public_key = age_keys.get("public_key")
|
||||
|
||||
for member in request.member_config.initial_members:
|
||||
invitation = await send_member_invitation(
|
||||
project_id, member, repository_info, member_service,
|
||||
request.name, age_public_key
|
||||
)
|
||||
member_invitations.append(invitation)
|
||||
|
||||
progress[4].status = "completed"
|
||||
progress[4].message = f"Sent {len(member_invitations)} member invitations"
|
||||
else:
|
||||
progress[4].status = "completed"
|
||||
progress[4].message = "No member invitations to send"
|
||||
|
||||
# Step 6: Finalization
|
||||
progress[5].status = "in_progress"
|
||||
progress[5].message = "Creating project record"
|
||||
|
||||
# Create project in database
|
||||
project_data = {
|
||||
"name": request.name,
|
||||
"description": request.description,
|
||||
"tags": request.tags,
|
||||
"git_url": repository_info.get("gitea_url") if repository_info else None,
|
||||
"git_owner": request.git_config.git_owner,
|
||||
"git_repository": request.git_config.repo_name or project_id,
|
||||
"git_branch": request.git_config.git_branch,
|
||||
"bzzz_enabled": request.bzzz_config.enable_bzzz,
|
||||
"private_repo": request.git_config.private,
|
||||
"metadata": {
|
||||
"template_id": request.template_id,
|
||||
"security_level": request.advanced_config.security_level,
|
||||
"created_via": "whoosh_setup_wizard",
|
||||
"age_keys_enabled": request.age_config.generate_new_key,
|
||||
"member_count": len(request.member_config.initial_members)
|
||||
}
|
||||
}
|
||||
|
||||
created_project = project_service.create_project(project_data)
|
||||
|
||||
progress[5].status = "completed"
|
||||
progress[5].message = "Project setup completed successfully"
|
||||
|
||||
# Generate next steps
|
||||
next_steps = []
|
||||
if repository_info:
|
||||
next_steps.append(f"Clone repository: git clone {repository_info['repository']['clone_url']}")
|
||||
if request.bzzz_config.enable_bzzz:
|
||||
next_steps.append("Create BZZZ tasks by adding issues with 'bzzz-task' label")
|
||||
if member_invitations:
|
||||
next_steps.append("Follow up on member invitation responses")
|
||||
next_steps.append("Configure project settings and workflows")
|
||||
|
||||
return ProjectSetupResponse(
|
||||
project_id=project_id,
|
||||
status="completed",
|
||||
progress=progress,
|
||||
repository=repository_info,
|
||||
age_keys=age_keys,
|
||||
member_invitations=member_invitations,
|
||||
next_steps=next_steps
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
# Update progress with error
|
||||
for step in progress:
|
||||
if step.status == "in_progress":
|
||||
step.status = "failed"
|
||||
step.message = f"Error: {str(e)}"
|
||||
break
|
||||
|
||||
raise HTTPException(status_code=500, detail=f"Project setup failed: {str(e)}")
|
||||
|
||||
async def generate_age_keys(project_id: str, age_config: AgeKeyConfig, age_service: AgeService) -> Optional[Dict[str, str]]:
|
||||
"""Generate Age master key pair using the Age service."""
|
||||
try:
|
||||
result = age_service.generate_master_key_pair(
|
||||
project_id=project_id,
|
||||
passphrase=age_config.master_key_passphrase
|
||||
)
|
||||
|
||||
# Create backup if location specified
|
||||
if age_config.key_backup_location:
|
||||
backup_success = age_service.backup_key(
|
||||
project_id=project_id,
|
||||
key_id=result["key_id"],
|
||||
backup_location=age_config.key_backup_location
|
||||
)
|
||||
result["backup_created"] = backup_success
|
||||
|
||||
# Generate recovery phrase
|
||||
recovery_phrase = age_service.generate_recovery_phrase(
|
||||
project_id=project_id,
|
||||
key_id=result["key_id"]
|
||||
)
|
||||
result["recovery_phrase"] = recovery_phrase
|
||||
|
||||
return {
|
||||
"key_id": result["key_id"],
|
||||
"public_key": result["public_key"],
|
||||
"private_key_stored": result["private_key_stored"],
|
||||
"backup_location": result["backup_location"],
|
||||
"recovery_phrase": recovery_phrase,
|
||||
"encrypted": result["encrypted"]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Age key generation failed: {e}")
|
||||
return None
|
||||
|
||||
async def send_member_invitation(project_id: str, member: ProjectMember, repository_info: Optional[Dict],
|
||||
member_service: MemberService, project_name: str, age_public_key: Optional[str] = None) -> Dict[str, str]:
|
||||
"""Send invitation to project member using the member service."""
|
||||
try:
|
||||
# Generate invitation
|
||||
invitation_result = member_service.generate_member_invitation(
|
||||
project_id=project_id,
|
||||
member_email=member.email,
|
||||
role=member.role,
|
||||
inviter_name="WHOOSH Project Setup",
|
||||
project_name=project_name,
|
||||
custom_message=member.invite_message
|
||||
)
|
||||
|
||||
if not invitation_result.get("created"):
|
||||
return {
|
||||
"email": member.email,
|
||||
"role": member.role,
|
||||
"invitation_sent": False,
|
||||
"error": invitation_result.get("error", "Failed to create invitation")
|
||||
}
|
||||
|
||||
# Send email invitation
|
||||
email_sent = member_service.send_email_invitation(invitation_result, age_public_key)
|
||||
|
||||
return {
|
||||
"email": member.email,
|
||||
"role": member.role,
|
||||
"invitation_sent": email_sent,
|
||||
"invitation_id": invitation_result["invitation_id"],
|
||||
"invitation_url": invitation_result["invitation_url"],
|
||||
"expires_at": invitation_result["expires_at"]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"email": member.email,
|
||||
"role": member.role,
|
||||
"invitation_sent": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
# === Age Key Management Endpoints ===
|
||||
|
||||
@router.get("/age-keys/{project_id}")
|
||||
async def get_project_age_keys(
|
||||
project_id: str,
|
||||
age_service: AgeService = Depends(get_age_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get Age keys for a project."""
|
||||
try:
|
||||
keys = age_service.list_project_keys(project_id)
|
||||
return {
|
||||
"project_id": project_id,
|
||||
"keys": keys,
|
||||
"count": len(keys)
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to retrieve Age keys: {str(e)}")
|
||||
|
||||
@router.post("/age-keys/{project_id}/validate")
|
||||
async def validate_age_key_access(
|
||||
project_id: str,
|
||||
key_id: str,
|
||||
age_service: AgeService = Depends(get_age_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate access to an Age key."""
|
||||
try:
|
||||
validation = age_service.validate_key_access(project_id, key_id)
|
||||
return validation
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Key validation failed: {str(e)}")
|
||||
|
||||
@router.post("/age-keys/{project_id}/backup")
|
||||
async def backup_age_key(
|
||||
project_id: str,
|
||||
key_id: str,
|
||||
backup_location: str,
|
||||
age_service: AgeService = Depends(get_age_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a backup of an Age key."""
|
||||
try:
|
||||
success = age_service.backup_key(project_id, key_id, backup_location)
|
||||
return {
|
||||
"project_id": project_id,
|
||||
"key_id": key_id,
|
||||
"backup_location": backup_location,
|
||||
"success": success
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Key backup failed: {str(e)}")
|
||||
|
||||
@router.post("/age-keys/{project_id}/encrypt")
|
||||
async def encrypt_data_with_age(
|
||||
project_id: str,
|
||||
data: str,
|
||||
recipients: List[str],
|
||||
age_service: AgeService = Depends(get_age_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""Encrypt data using Age with specified recipients."""
|
||||
try:
|
||||
encrypted_data = age_service.encrypt_data(data, recipients)
|
||||
return {
|
||||
"project_id": project_id,
|
||||
"encrypted_data": encrypted_data,
|
||||
"recipients": recipients
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Data encryption failed: {str(e)}")
|
||||
Reference in New Issue
Block a user