diff --git a/src/nsls2api/api/models/proposal_model.py b/src/nsls2api/api/models/proposal_model.py index b426e65b..496b7739 100644 --- a/src/nsls2api/api/models/proposal_model.py +++ b/src/nsls2api/api/models/proposal_model.py @@ -163,4 +163,20 @@ class ProposalIdDataSessionList(pydantic.BaseModel): proposals: list[ProposalIdDataSession] count: int page_size: int - page: int \ No newline at end of file + page: int + +class ProposalSummaryForUser(pydantic.BaseModel): + proposal_id: str + title: str + saf_ids: list[str] + principal_investigator: Optional[User] + instruments: Optional[list[str]] + cycles: Optional[list[str]] + data_session: Optional[str] + +class UserProposalsList(pydantic.BaseModel): + username: str + count: int + page: int + page_size: int + proposals: list[ProposalSummaryForUser] diff --git a/src/nsls2api/api/v1/user_api.py b/src/nsls2api/api/v1/user_api.py index 539ed96a..854950d1 100644 --- a/src/nsls2api/api/v1/user_api.py +++ b/src/nsls2api/api/v1/user_api.py @@ -1,13 +1,20 @@ import asyncio - import fastapi -from fastapi import HTTPException, Request, Header + +from typing import Annotated + +from fastapi import Depends,Header, HTTPException, Request, Query from nsls2api.api.models.person_model import DataSessionAccess, LDAPUserResponse, Person +from nsls2api.api.models.proposal_model import Proposal, ProposalSummaryForUser, UserProposalsList +from nsls2api.infrastructure.security import ( + get_current_user, +) from nsls2api.infrastructure.security import get_settings from nsls2api.services import ( bnlpeople_service, person_service, + proposal_service, ) from nsls2api.services.ldap_service import get_user_info, shape_ldap_response @@ -106,3 +113,44 @@ async def get_myself(upn: str = Header(...)): async def get_data_sessions_by_username(username: str): data_access = await person_service.data_sessions_by_username(username) return data_access + + +@router.get("/person/proposals", response_model=UserProposalsList, summary="Fetch proposals for a user including, SAF ID's, PI details") +async def get_proposals_for_username( + username: str = Header(..., description="Username to fetch proposals for"), + page_size: int = Query(10, ge=1, le=200), + page: int = Query(1, ge=1), +): + if not username: + raise HTTPException(status_code=400, detail="Username header is required") + + current_cycle, proposals = await proposal_service.fetch_proposals_for_username( + username, page_size=page_size, page=page + ) + + if current_cycle is None: + raise HTTPException(status_code=404, detail="No current operating cycle found") + + proposal_summaries = [] + for proposal in proposals: + pi = next((u for u in proposal.users if u.is_pi), None) + saf_ids = [s.saf_id for s in (proposal.safs or []) if s.saf_id] + proposal_summaries.append( + ProposalSummaryForUser( + proposal_id=proposal.proposal_id, + title=proposal.title, + saf_ids=saf_ids, + principal_investigator=pi, + instruments=proposal.instruments, + cycles=proposal.cycles, + data_session=proposal.data_session, + ) + ) + + return UserProposalsList( + username=username, + count=len(proposal_summaries), + page=page, + page_size=page_size, + proposals=proposal_summaries, + ) diff --git a/src/nsls2api/services/proposal_service.py b/src/nsls2api/services/proposal_service.py index 2d197cf7..c29e7936 100644 --- a/src/nsls2api/services/proposal_service.py +++ b/src/nsls2api/services/proposal_service.py @@ -807,3 +807,24 @@ async def generate_fake_test_proposal( await Proposal.insert_one(proposal) return proposal + +async def fetch_proposals_for_username(username: str, page_size: int = 10, + page: int = 1) -> tuple[str | None, list[Proposal]]: + """Retrieve all proposals associated with given username for current operating cycle""" + + current_cycle = await facility_service.current_operating_cycle(FacilityName.nsls2) + + if not current_cycle: return None, [] + + proposals = ( + await Proposal.find( + And( + ElemMatch(Proposal.users, {"username": username}), + In(Proposal.cycles, [current_cycle]), + ) + ) + .limit(page_size) + .skip(page_size * (page - 1)) + .to_list() + ) + return current_cycle, proposals