"""Collaboration API routes for VoIdea. Voting, comments, activity log, and notifications. """ from typing import Annotated from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.dependencies import get_current_user, get_db from app.models.collaboration import Notification from app.models.user import User from app.schemas.collaboration import ( ActivityResponse, CommentCreate, CommentResponse, NotificationResponse, NotificationUnreadCount, VoteCount, VoteResponse, VoteToggleRequest, ) from app.services.collaboration_service import CollaborationService from app.services.idea_service import IdeaService router = APIRouter() @router.post("/ideas/{idea_id}/vote", response_model=VoteCount) async def toggle_vote( idea_id: str, body: VoteToggleRequest, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) 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") await svc.toggle_vote(UUID(idea_id), user.id, body.vote) counts = await svc.get_vote_counts(UUID(idea_id)) user_vote = await svc.get_user_vote(UUID(idea_id), user.id) return VoteCount(**counts, user_vote=user_vote) @router.get("/ideas/{idea_id}/votes", response_model=VoteCount) async def get_votes( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) 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") counts = await svc.get_vote_counts(UUID(idea_id)) user_vote = await svc.get_user_vote(UUID(idea_id), user.id) return VoteCount(**counts, user_vote=user_vote) # ── Comments ── @router.get("/ideas/{idea_id}/comments", response_model=list[CommentResponse]) async def list_comments( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) return await svc.list_comments(UUID(idea_id)) @router.post("/ideas/{idea_id}/comments", response_model=CommentResponse, status_code=status.HTTP_201_CREATED) async def create_comment( idea_id: str, body: CommentCreate, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) parent_id = UUID(body.parent_id) if body.parent_id else None c = await svc.create_comment(UUID(idea_id), user.id, body.content, parent_id) await svc.log_activity( UUID(idea_id), user.id, "comment", {"comment_id": str(c.id), "preview": body.content[:100]}, ) return CommentResponse( id=str(c.id), idea_id=str(c.idea_id), user_id=str(c.user_id), content=c.content, parent_id=str(c.parent_id) if c.parent_id else None, display_name=user.display_name, avatar_url=user.avatar_url, created_at=c.created_at, updated_at=c.updated_at, ) @router.delete("/ideas/{idea_id}/comments/{comment_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_comment( idea_id: str, comment_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) success = await svc.delete_comment(UUID(comment_id), user.id) if not success: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found") # ── Activity ── @router.get("/ideas/{idea_id}/activity", response_model=list[ActivityResponse]) async def list_activity( idea_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), limit: int = Query(50, ge=1, le=200), ): svc = CollaborationService(db) return await svc.list_activities(UUID(idea_id), limit=limit) # ── Notifications ── @router.get("/notifications", response_model=list[NotificationResponse]) async def list_notifications( user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), unread_only: bool = Query(False), ): svc = CollaborationService(db) notifications = await svc.list_notifications(user.id, unread_only=unread_only) return [ NotificationResponse( id=str(n.id), type=n.type, title=n.title, body=n.body, link=n.link, is_read=n.is_read, created_at=n.created_at, ) for n in notifications ] @router.get("/notifications/unread-count", response_model=NotificationUnreadCount) async def unread_count( user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) count = await svc.get_unread_count(user.id) return NotificationUnreadCount(count=count) @router.post("/notifications/{notification_id}/read", response_model=NotificationResponse) async def mark_read( notification_id: str, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) success = await svc.mark_notification_read(UUID(notification_id), user.id) if not success: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Notification not found") result = await db.execute( select(Notification).where(Notification.id == UUID(notification_id)) ) n = result.scalar_one() return NotificationResponse( id=str(n.id), type=n.type, title=n.title, body=n.body, link=n.link, is_read=n.is_read, created_at=n.created_at, ) @router.post("/notifications/read-all", response_model=NotificationUnreadCount) async def mark_all_read( user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = CollaborationService(db) count = await svc.mark_all_read(user.id) return NotificationUnreadCount(count=count)