feat: deploy infrastructure + disk/drive, calendar, presentation, workspaces, onboarding, demo user
This commit is contained in:
@@ -14,14 +14,18 @@ from app.models.idea import Idea
|
||||
from app.models.user import User
|
||||
from app.schemas.idea import (
|
||||
AnalysisResultResponse,
|
||||
FunnelTransitionRequest,
|
||||
IdeaAnalyzeResponse,
|
||||
IdeaCreate,
|
||||
IdeaResponse,
|
||||
IdeaUpdate,
|
||||
)
|
||||
from app.services.analysis_service import AnalysisService
|
||||
from app.services.collaboration_service import CollaborationService
|
||||
from app.services.disk_service import DiskService
|
||||
from app.services.export_service import export_content
|
||||
from app.services.idea_service import IdeaService
|
||||
from app.services.presentation_service import PresentationService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -186,6 +190,65 @@ async def export_idea(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{idea_id}/presentation")
|
||||
async def generate_presentation(
|
||||
idea_id: str,
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
roles: str = Query("", description="Comma-separated role names (empty=all)"),
|
||||
destination: str = Query("download", regex="^(download|drive)$"),
|
||||
drive_provider: str = Query("", description="Specific drive provider (google/yandex/apple)"),
|
||||
):
|
||||
"""Generate HTML presentation for an idea.
|
||||
|
||||
Args:
|
||||
destination: "download" = return as file, "drive" = upload to cloud drive
|
||||
roles: Comma-separated agent roles (empty = all available)
|
||||
drive_provider: Specific provider to upload to (empty = auto-detect)
|
||||
"""
|
||||
idea_svc = IdeaService(db)
|
||||
idea = await idea_svc.get_by_id(idea_id)
|
||||
if not idea or str(idea.user_id) != str(user.id):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Idea not found")
|
||||
|
||||
role_list = [r.strip() for r in roles.split(",") if r.strip()] if roles else None
|
||||
pres_svc = PresentationService(db)
|
||||
html = await pres_svc.generate(idea_id, roles=role_list, user_name=user.display_name)
|
||||
if not html:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Idea not found")
|
||||
|
||||
filename = f"prezentaciya_{idea.title[:40].replace(' ', '_')}.html"
|
||||
|
||||
if destination == "drive":
|
||||
disk_svc = DiskService(db)
|
||||
content_bytes = html.encode("utf-8")
|
||||
if drive_provider:
|
||||
success = await disk_svc.upload_to_provider(
|
||||
user, drive_provider, filename, content_bytes
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Failed to upload to {drive_provider}",
|
||||
)
|
||||
return {"success": True, "provider": drive_provider, "filename": filename}
|
||||
else:
|
||||
result = await disk_svc.upload_to_any(user, filename, content_bytes)
|
||||
if not result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No connected drive. Connect a cloud drive first.",
|
||||
)
|
||||
return {"success": True, "provider": result["provider"], "filename": filename}
|
||||
|
||||
from fastapi.responses import Response
|
||||
return Response(
|
||||
content=html,
|
||||
media_type="text/html; charset=utf-8",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{idea_id}/share", response_model=IdeaResponse)
|
||||
async def toggle_idea_share(
|
||||
idea_id: str,
|
||||
@@ -208,6 +271,39 @@ async def toggle_idea_share(
|
||||
return _idea_to_response(idea)
|
||||
|
||||
|
||||
@router.post("/{idea_id}/funnel", response_model=IdeaResponse)
|
||||
async def transition_funnel(
|
||||
idea_id: str,
|
||||
body: FunnelTransitionRequest,
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
service = IdeaService(db)
|
||||
idea = await service.get_by_id(idea_id)
|
||||
if not idea or str(idea.user_id) != str(user.id):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Idea not found")
|
||||
|
||||
target = body.target_status
|
||||
valid_states = Idea.FUNNEL_STATES
|
||||
if target not in valid_states:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid funnel status. Valid: {', '.join(valid_states)}",
|
||||
)
|
||||
|
||||
idea.funnel_status = target
|
||||
await db.commit()
|
||||
await db.refresh(idea)
|
||||
|
||||
collab = CollaborationService(db)
|
||||
await collab.log_activity(
|
||||
UUID(idea_id), user.id, "funnel",
|
||||
{"from": idea.funnel_status, "to": target},
|
||||
)
|
||||
|
||||
return _idea_to_response(idea)
|
||||
|
||||
|
||||
@router.post("/demo", response_model=IdeaResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_demo_idea(
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
|
||||
Reference in New Issue
Block a user