from fastapi import APIRouter, Depends, HTTPException, Query, status from motor.motor_asyncio import AsyncIOMotorDatabase from sqlalchemy.orm import Session from app.database import get_db from app.dependencies import get_current_user, get_mongo_db_dep from app.models.user import User from app.schemas.document import DocumentCreate, DocumentResponse, DocumentUpdate, ShareRequest from app.services.document_service import ( add_member, create_document, delete_document, get_document, list_documents, record_visit, remove_member, search_documents, update_document, ) from app.services.user_service import get_user_by_email_or_username router = APIRouter(prefix="/documents", tags=["documents"]) @router.get("", response_model=list[DocumentResponse]) async def list_docs( type: str | None = Query(None, description="Filter by document type"), stead_id: str | None = Query(None, description="Filter by parent stead"), starred: bool | None = Query(None, description="Filter starred projects"), sort: str = Query("updated_at", description="Sort field: updated_at or last_visited_at"), current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> list[DocumentResponse]: sort_field = "last_visited_at" if sort == "last_visited_at" else "updated_at" docs = await list_documents( db, current_user.id, doc_type=type, stead_id=stead_id, starred=starred, sort=sort_field, ) return [DocumentResponse(**doc) for doc in docs] @router.get("/search", response_model=list[DocumentResponse]) async def search_docs( q: str = Query("", min_length=0), current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> list[DocumentResponse]: docs = await search_documents(db, current_user.id, q) return [DocumentResponse(**doc) for doc in docs] @router.get("/{doc_id}", response_model=DocumentResponse) async def get_doc( doc_id: str, current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> DocumentResponse: doc = await get_document(db, doc_id, current_user.id) if doc is None: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Document not found or access denied") return DocumentResponse(**doc) @router.post("", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED) async def create_doc( data: DocumentCreate, current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> DocumentResponse: created_by = current_user.full_name or current_user.username doc = await create_document(db, current_user.id, created_by, data) return DocumentResponse(**doc) @router.patch("/{doc_id}", response_model=DocumentResponse) async def update_doc( doc_id: str, data: DocumentUpdate, current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> DocumentResponse: doc = await update_document(db, doc_id, current_user.id, data) if doc is None: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Document not found or you do not have edit access", ) return DocumentResponse(**doc) @router.post("/{doc_id}/visit", response_model=DocumentResponse) async def visit_doc( doc_id: str, current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> DocumentResponse: doc = await record_visit(db, doc_id, current_user.id) if doc is None: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Document not found or access denied") return DocumentResponse(**doc) @router.post("/{doc_id}/share", response_model=DocumentResponse) async def share_doc( doc_id: str, data: ShareRequest, current_user: User = Depends(get_current_user), mongo: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), sql: Session = Depends(get_db), ) -> DocumentResponse: identifier = data.email_or_username.strip().lower() user = get_user_by_email_or_username(sql, identifier) if user is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") if user.id == current_user.id: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="You already own this document") doc = await add_member( mongo, doc_id, current_user.id, { "user_id": user.id, "name": user.full_name or user.username, "email": user.email, }, data.role, ) if doc is None: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only the owner can share this document") return DocumentResponse(**doc) @router.delete("/{doc_id}/share/{user_id}", response_model=DocumentResponse) async def unshare_doc( doc_id: str, user_id: int, current_user: User = Depends(get_current_user), mongo: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> DocumentResponse: doc = await remove_member(mongo, doc_id, current_user.id, user_id) if doc is None: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only the owner can update sharing") return DocumentResponse(**doc) @router.delete("/{doc_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_doc( doc_id: str, current_user: User = Depends(get_current_user), db: AsyncIOMotorDatabase = Depends(get_mongo_db_dep), ) -> None: deleted = await delete_document(db, doc_id, current_user.id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found")