import uuid from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models.project import Project from app.schemas.project import ProjectCreate, ProjectListResponse, ProjectResponse, ProjectUpdate from app.routers.auth import get_current_agent router = APIRouter(prefix="/api/v1/projects", tags=["projects"]) @router.get("", response_model=ProjectListResponse) async def list_projects( request: Request, db: AsyncSession = Depends(get_db), ): agent = await get_current_agent(request, db) result = await db.execute( select(Project).where( Project.agent_id == agent.id, Project.is_deleted == False, ).order_by(Project.created_at.desc()) ) projects = result.scalars().all() return ProjectListResponse(projects=[ProjectResponse.model_validate(p) for p in projects]) @router.post("", response_model=ProjectResponse, status_code=201) async def create_project( request: Request, payload: ProjectCreate, db: AsyncSession = Depends(get_db), ): agent = await get_current_agent(request, db) project = Project( id=str(uuid.uuid4()), name=payload.name, description=payload.description, agent_id=agent.id, ) db.add(project) await db.flush() return ProjectResponse.model_validate(project) @router.get("/{project_id}", response_model=ProjectResponse) async def get_project( request: Request, project_id: str, db: AsyncSession = Depends(get_db), ): agent = await get_current_agent(request, db) result = await db.execute( select(Project).where( Project.id == project_id, Project.agent_id == agent.id, Project.is_deleted == False, ) ) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="Project not found") return ProjectResponse.model_validate(project) @router.put("/{project_id}", response_model=ProjectResponse) async def update_project( request: Request, project_id: str, payload: ProjectUpdate, db: AsyncSession = Depends(get_db), ): agent = await get_current_agent(request, db) result = await db.execute( select(Project).where( Project.id == project_id, Project.agent_id == agent.id, Project.is_deleted == False, ) ) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="Project not found") if payload.name is not None: project.name = payload.name if payload.description is not None: project.description = payload.description project.updated_at = datetime.utcnow() await db.flush() return ProjectResponse.model_validate(project) @router.patch("/{project_id}", response_model=ProjectResponse) async def patch_project( request: Request, project_id: str, payload: ProjectUpdate, db: AsyncSession = Depends(get_db), ): """Partial update of project name and/or description.""" agent = await get_current_agent(request, db) result = await db.execute( select(Project).where( Project.id == project_id, Project.agent_id == agent.id, Project.is_deleted == False, ) ) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="Project not found") if payload.name is not None: project.name = payload.name if payload.description is not None: project.description = payload.description project.updated_at = datetime.utcnow() await db.flush() return ProjectResponse.model_validate(project) @router.delete("/{project_id}", status_code=204) async def delete_project( request: Request, project_id: str, db: AsyncSession = Depends(get_db), ): agent = await get_current_agent(request, db) result = await db.execute( select(Project).where( Project.id == project_id, Project.agent_id == agent.id, Project.is_deleted == False, ) ) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="Project not found") project.is_deleted = True project.deleted_at = datetime.utcnow() project.deleted_by = agent.id await db.flush() return None @router.post("/{project_id}/restore", response_model=ProjectResponse) async def restore_project( request: Request, project_id: str, db: AsyncSession = Depends(get_db), ): agent = await get_current_agent(request, db) result = await db.execute( select(Project).where( Project.id == project_id, Project.agent_id == agent.id, Project.is_deleted == True, ) ) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="Project not found") project.is_deleted = False project.deleted_at = None project.deleted_by = None await db.flush() return ProjectResponse.model_validate(project)