feat: deploy infrastructure + disk/drive, calendar, presentation, workspaces, onboarding, demo user

This commit is contained in:
2026-05-19 16:26:26 +03:00
parent 688d043dad
commit 3529c39b22
304 changed files with 18826 additions and 1176 deletions
+96
View File
@@ -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)],