from typing import List, Optional from bson import ObjectId from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from api.dependency import get_dao from api.endpoints.auth import get_current_user from models.Project import Project from repos.dao import DAO router = APIRouter(prefix="/api/projects", tags=["Projects"]) class ProjectCreate(BaseModel): name: str description: Optional[str] = None class ProjectMemberResponse(BaseModel): id: str username: str class ProjectResponse(BaseModel): id: str name: str description: Optional[str] = None owner_id: str members: List[ProjectMemberResponse] is_owner: bool = False async def _get_project_response(project: Project, current_user_id: str, dao: DAO) -> ProjectResponse: member_responses = [] for member_id in project.members: # We need a way to get user by ID. Let's check UsersRepo for get_user by ObjectId or string. # Currently UsersRepo has get_user(user_id: int) for Telegram IDs. # But for Web users we might need to search by _id. # Let's try to get user info. # Since project.members contains strings (ObjectIds as strings), we search by _id. user_doc = await dao.users.collection.find_one({"_id": ObjectId(member_id)}) if not user_doc and member_id.isdigit(): # Fallback for telegram IDs if they are stored as strings of digits user_doc = await dao.users.get_user(int(member_id)) username = "unknown" if user_doc: username = user_doc.get("username", "unknown") member_responses.append(ProjectMemberResponse(id=member_id, username=username)) return ProjectResponse( id=project.id, name=project.name, description=project.description, owner_id=project.owner_id, members=member_responses, is_owner=(project.owner_id == current_user_id) ) @router.post("", response_model=ProjectResponse) async def create_project( project_data: ProjectCreate, dao: DAO = Depends(get_dao), current_user: dict = Depends(get_current_user) ): user_id = str(current_user["_id"]) new_project = Project( name=project_data.name, description=project_data.description, owner_id=user_id, members=[user_id] ) project_id = await dao.projects.create_project(new_project) new_project.id = project_id # Add project to user's project list await dao.users.collection.update_one( {"_id": current_user["_id"]}, {"$addToSet": {"project_ids": project_id}} ) return await _get_project_response(new_project, user_id, dao) @router.get("", response_model=List[ProjectResponse]) async def get_my_projects( dao: DAO = Depends(get_dao), current_user: dict = Depends(get_current_user) ): user_id = str(current_user["_id"]) projects = await dao.projects.get_projects_by_user(user_id) responses = [] for p in projects: responses.append(await _get_project_response(p, user_id, dao)) return responses class MemberAdd(BaseModel): username: str @router.post("/{project_id}/members", dependencies=[Depends(get_current_user)]) async def add_member( project_id: str, member_data: MemberAdd, dao: DAO = Depends(get_dao), current_user: dict = Depends(get_current_user) ): user_id = str(current_user["_id"]) project = await dao.projects.get_project(project_id) if not project: raise HTTPException(status_code=404, detail="Project not found") if project.owner_id != user_id: raise HTTPException(status_code=403, detail="Only owner can add members") target_user = await dao.users.get_user_by_username(member_data.username) if not target_user: raise HTTPException(status_code=404, detail="User not found") target_user_id = str(target_user["_id"]) if target_user_id in project.members: return {"message": "User already in project"} await dao.projects.add_member(project_id, target_user_id) # Update target user's project list await dao.users.collection.update_one( {"_id": target_user["_id"]}, {"$addToSet": {"project_ids": project_id}} ) return {"message": "Member added"} @router.post("/{project_id}/join", dependencies=[Depends(get_current_user)]) async def join_project( project_id: str, dao: DAO = Depends(get_dao), current_user: dict = Depends(get_current_user) ): # Retrieve project to verify it exists project = await dao.projects.get_project(project_id) if not project: raise HTTPException(status_code=404, detail="Project not found") user_id = str(current_user["_id"]) # Check if user is ALREADY in project if user_id in project.members: return {"message": "Already a member"} # Add member await dao.projects.add_member(project_id, user_id) # Update user's project list await dao.users.collection.update_one( {"_id": current_user["_id"]}, {"$addToSet": {"project_ids": project_id}} ) return {"message": "Joined project"} @router.delete("/{project_id}", dependencies=[Depends(get_current_user)] ) async def delete_project( project_id: str, dao: DAO = Depends(get_dao), current_user: dict = Depends(get_current_user) ): user_id = str(current_user["_id"]) project = await dao.projects.get_project(project_id) if not project: raise HTTPException(status_code=404, detail="Project not found") if project.owner_id != user_id: raise HTTPException(status_code=403, detail="Only owner can delete project") await dao.projects.delete_project(project_id) # Remove project from user's project list await dao.users.collection.update_one( {"_id": current_user["_id"]}, {"$pull": {"project_ids": project_id}} ) return {"message": "Project deleted"}