"""Ideas API routes for VoIdea.""" from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from fastapi.responses import PlainTextResponse from uuid import uuid4 from app.core.dependencies import get_current_user, get_db, get_optional_user from app.models.idea import Idea from app.models.user import User from app.schemas.idea import ( AnalysisResultResponse, IdeaAnalyzeResponse, IdeaCreate, IdeaResponse, IdeaUpdate, ) from app.services.analysis_service import AnalysisService from app.services.export_service import export_content from app.services.idea_service import IdeaService router = APIRouter() def _idea_to_response(idea) -> IdeaResponse: return IdeaResponse( id=str(idea.id), user_id=str(idea.user_id), title=idea.title, content=idea.content, status=idea.status, tags=idea.tags, is_public=idea.is_public, public_slug=idea.public_slug, created_at=idea.created_at, updated_at=idea.updated_at, ) @router.get("/", response_model=list[IdeaResponse]) async def list_ideas( user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=100), ): service = IdeaService(db) ideas = await service.list_by_user(str(user.id), skip=skip, limit=limit) return [_idea_to_response(idea) for idea in ideas] @router.post("/", response_model=IdeaResponse, status_code=status.HTTP_201_CREATED) async def create_idea( body: IdeaCreate, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): service = IdeaService(db) idea = await service.create( user_id=str(user.id), title=body.title, content=body.content, tags=body.tags, is_public=body.is_public, ) return _idea_to_response(idea) @router.get("/{idea_id}", response_model=IdeaResponse) async def get_idea( idea_id: str, 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") return _idea_to_response(idea) @router.patch("/{idea_id}", response_model=IdeaResponse) async def update_idea( idea_id: str, body: IdeaUpdate, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): service = IdeaService(db) idea = await service.update( idea_id, str(user.id), title=body.title, content=body.content, status=body.status, tags=body.tags, is_public=body.is_public, ) if not idea: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Idea not found") return _idea_to_response(idea) @router.delete("/{idea_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_idea( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): service = IdeaService(db) deleted = await service.delete(idea_id, str(user.id)) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Idea not found") @router.post("/{idea_id}/analyze", response_model=IdeaAnalyzeResponse) async def analyze_idea( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): idea_service = IdeaService(db) idea = await idea_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") analysis_service = AnalysisService(db) result = await analysis_service.start_analysis(idea_id) return IdeaAnalyzeResponse( status="started", idea_id=idea_id, task_count=result["task_count"], tasks=result["tasks"], ) @router.get("/{idea_id}/analysis", response_model=list[AnalysisResultResponse]) async def get_analysis_results( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), role: str = Query(None, description="Filter by agent role"), ): idea_service = IdeaService(db) idea = await idea_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") analysis_service = AnalysisService(db) results = await analysis_service.get_analysis_results(idea_id, role=role) return [AnalysisResultResponse(**r) for r in results] @router.get("/{idea_id}/export") async def export_idea( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), format: str = Query("md", regex="^(json|md|html)$"), ): idea_service = IdeaService(db) idea = await idea_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") metadata = { "ID": str(idea.id), "Статус": idea.status, "Теги": ", ".join(idea.tags) if idea.tags else "—", "Создано": idea.created_at.isoformat() if idea.created_at else "—", } body, content_type, ext = export_content(format, idea.title, idea.content, metadata) filename = f"{idea.title[:50]}.{ext}".replace(" ", "_") from fastapi.responses import Response return Response( content=body, media_type=content_type, headers={"Content-Disposition": f'attachment; filename="{filename}"'}, ) @router.post("/{idea_id}/share", response_model=IdeaResponse) async def toggle_idea_share( idea_id: str, 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") idea.is_public = not idea.is_public if idea.is_public and not idea.public_slug: idea.public_slug = uuid4().hex[:16] elif not idea.is_public: idea.public_slug = None await db.commit() await db.refresh(idea) 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)], db: AsyncSession = Depends(get_db), ): service = IdeaService(db) idea = await service.create( user_id=str(user.id), title="Моя первая идея (демо)", content="Пример идеи для знакомства с VoIdeaAI. Замените этот текст на свою идею.\n\n" "VoIdeaAI поможет проанализировать её с разных сторон: бизнес, финансы, " "право, технологии и маркетинг. Просто нажмите «Анализ» на странице идеи.", tags=["demo", "привет"], is_public=False, ) return _idea_to_response(idea) @router.get("/shared/{slug}", response_model=IdeaResponse) async def get_shared_idea( slug: str, db: AsyncSession = Depends(get_db), ): result = await db.execute( select(Idea).where(Idea.public_slug == slug, Idea.is_public == True) ) idea = result.scalar_one_or_none() if not idea: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Idea not found or not shared") return _idea_to_response(idea)