848 lines
31 KiB
Python
848 lines
31 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Routes for Trustee feature data management.
|
|
Implements CRUD operations for organisations, roles, access, contracts, documents, and positions.
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Request, Query, Response
|
|
from fastapi.responses import StreamingResponse
|
|
from typing import List, Dict, Any, Optional
|
|
from fastapi import status
|
|
import logging
|
|
import json
|
|
import io
|
|
|
|
from modules.auth import limiter, getRequestContext, RequestContext
|
|
from modules.interfaces.interfaceDbTrusteeObjects import getInterface
|
|
from modules.datamodels.datamodelTrustee import (
|
|
TrusteeOrganisation,
|
|
TrusteeRole,
|
|
TrusteeAccess,
|
|
TrusteeContract,
|
|
TrusteeDocument,
|
|
TrusteePosition,
|
|
TrusteePositionDocument,
|
|
)
|
|
from modules.datamodels.datamodelPagination import (
|
|
PaginationParams,
|
|
PaginatedResponse,
|
|
PaginationMetadata,
|
|
normalize_pagination_dict,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(
|
|
prefix="/api/trustee",
|
|
tags=["Trustee"],
|
|
responses={404: {"description": "Not found"}}
|
|
)
|
|
|
|
|
|
# ===== Helper Functions =====
|
|
|
|
def _parsePagination(pagination: Optional[str]) -> Optional[PaginationParams]:
|
|
"""Parse pagination parameter from JSON string."""
|
|
if not pagination:
|
|
return None
|
|
try:
|
|
paginationDict = json.loads(pagination)
|
|
if paginationDict:
|
|
paginationDict = normalize_pagination_dict(paginationDict)
|
|
return PaginationParams(**paginationDict)
|
|
except (json.JSONDecodeError, ValueError) as e:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Invalid pagination parameter: {str(e)}"
|
|
)
|
|
return None
|
|
|
|
|
|
# ===== Organisation Routes =====
|
|
|
|
@router.get("/organisations", response_model=PaginatedResponse[TrusteeOrganisation])
|
|
@limiter.limit("30/minute")
|
|
async def getOrganisations(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams"),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteeOrganisation]:
|
|
"""Get all organisations with optional pagination."""
|
|
logger = logging.getLogger(__name__)
|
|
logger.debug(f"getOrganisations called for user {context.user.id}, mandateId: {context.mandateId}")
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllOrganisations(paginationParams)
|
|
logger.debug(f"getOrganisations returned {len(result.items)} items")
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/organisations/{orgId}", response_model=TrusteeOrganisation)
|
|
@limiter.limit("30/minute")
|
|
async def getOrganisation(
|
|
request: Request,
|
|
orgId: str = Path(..., description="Organisation ID"),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeOrganisation:
|
|
"""Get a single organisation by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
org = interface.getOrganisation(orgId)
|
|
if not org:
|
|
raise HTTPException(status_code=404, detail=f"Organisation {orgId} not found")
|
|
return org
|
|
|
|
|
|
@router.post("/organisations", response_model=TrusteeOrganisation, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createOrganisation(
|
|
request: Request,
|
|
data: TrusteeOrganisation = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeOrganisation:
|
|
"""Create a new organisation."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createOrganisation(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create organisation")
|
|
return result
|
|
|
|
|
|
@router.put("/organisations/{orgId}", response_model=TrusteeOrganisation)
|
|
@limiter.limit("10/minute")
|
|
async def updateOrganisation(
|
|
request: Request,
|
|
orgId: str = Path(..., description="Organisation ID"),
|
|
data: TrusteeOrganisation = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeOrganisation:
|
|
"""Update an organisation."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getOrganisation(orgId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Organisation {orgId} not found")
|
|
|
|
result = interface.updateOrganisation(orgId, data.model_dump(exclude={"id"}))
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to update organisation")
|
|
return result
|
|
|
|
|
|
@router.delete("/organisations/{orgId}")
|
|
@limiter.limit("10/minute")
|
|
async def deleteOrganisation(
|
|
request: Request,
|
|
orgId: str = Path(..., description="Organisation ID"),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete an organisation."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getOrganisation(orgId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Organisation {orgId} not found")
|
|
|
|
success = interface.deleteOrganisation(orgId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete organisation")
|
|
return {"message": f"Organisation {orgId} deleted"}
|
|
|
|
|
|
# ===== Role Routes =====
|
|
|
|
@router.get("/roles", response_model=PaginatedResponse[TrusteeRole])
|
|
@limiter.limit("30/minute")
|
|
async def getRoles(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteeRole]:
|
|
"""Get all roles with optional pagination."""
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllRoles(paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/roles/{roleId}", response_model=TrusteeRole)
|
|
@limiter.limit("30/minute")
|
|
async def getRole(
|
|
request: Request,
|
|
roleId: str = Path(..., description="Role ID"),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeRole:
|
|
"""Get a single role by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
role = interface.getRole(roleId)
|
|
if not role:
|
|
raise HTTPException(status_code=404, detail=f"Role {roleId} not found")
|
|
return role
|
|
|
|
|
|
@router.post("/roles", response_model=TrusteeRole, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createRole(
|
|
request: Request,
|
|
data: TrusteeRole = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeRole:
|
|
"""Create a new role (sysadmin only)."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createRole(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create role")
|
|
return result
|
|
|
|
|
|
@router.put("/roles/{roleId}", response_model=TrusteeRole)
|
|
@limiter.limit("10/minute")
|
|
async def updateRole(
|
|
request: Request,
|
|
roleId: str = Path(...),
|
|
data: TrusteeRole = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeRole:
|
|
"""Update a role (sysadmin only)."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getRole(roleId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Role {roleId} not found")
|
|
|
|
result = interface.updateRole(roleId, data.model_dump(exclude={"id"}))
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to update role")
|
|
return result
|
|
|
|
|
|
@router.delete("/roles/{roleId}")
|
|
@limiter.limit("10/minute")
|
|
async def deleteRole(
|
|
request: Request,
|
|
roleId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a role (sysadmin only, fails if in use)."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getRole(roleId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Role {roleId} not found")
|
|
|
|
success = interface.deleteRole(roleId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete role (may be in use)")
|
|
return {"message": f"Role {roleId} deleted"}
|
|
|
|
|
|
# ===== Access Routes =====
|
|
|
|
@router.get("/access", response_model=PaginatedResponse[TrusteeAccess])
|
|
@limiter.limit("30/minute")
|
|
async def getAllAccess(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteeAccess]:
|
|
"""Get all access records with optional pagination."""
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllAccess(paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/access/{accessId}", response_model=TrusteeAccess)
|
|
@limiter.limit("30/minute")
|
|
async def getAccess(
|
|
request: Request,
|
|
accessId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeAccess:
|
|
"""Get a single access record by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
access = interface.getAccess(accessId)
|
|
if not access:
|
|
raise HTTPException(status_code=404, detail=f"Access {accessId} not found")
|
|
return access
|
|
|
|
|
|
@router.get("/access/organisation/{orgId}", response_model=List[TrusteeAccess])
|
|
@limiter.limit("30/minute")
|
|
async def getAccessByOrganisation(
|
|
request: Request,
|
|
orgId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteeAccess]:
|
|
"""Get all access records for an organisation."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getAccessByOrganisation(orgId)
|
|
|
|
|
|
@router.get("/access/user/{userId}", response_model=List[TrusteeAccess])
|
|
@limiter.limit("30/minute")
|
|
async def getAccessByUser(
|
|
request: Request,
|
|
userId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteeAccess]:
|
|
"""Get all access records for a user."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getAccessByUser(userId)
|
|
|
|
|
|
@router.post("/access", response_model=TrusteeAccess, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createAccess(
|
|
request: Request,
|
|
data: TrusteeAccess = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeAccess:
|
|
"""Create a new access record."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createAccess(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create access")
|
|
return result
|
|
|
|
|
|
@router.put("/access/{accessId}", response_model=TrusteeAccess)
|
|
@limiter.limit("10/minute")
|
|
async def updateAccess(
|
|
request: Request,
|
|
accessId: str = Path(...),
|
|
data: TrusteeAccess = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeAccess:
|
|
"""Update an access record."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getAccess(accessId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Access {accessId} not found")
|
|
|
|
result = interface.updateAccess(accessId, data.model_dump(exclude={"id"}))
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to update access")
|
|
return result
|
|
|
|
|
|
@router.delete("/access/{accessId}")
|
|
@limiter.limit("10/minute")
|
|
async def deleteAccess(
|
|
request: Request,
|
|
accessId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete an access record."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getAccess(accessId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Access {accessId} not found")
|
|
|
|
success = interface.deleteAccess(accessId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete access")
|
|
return {"message": f"Access {accessId} deleted"}
|
|
|
|
|
|
# ===== Contract Routes =====
|
|
|
|
@router.get("/contracts", response_model=PaginatedResponse[TrusteeContract])
|
|
@limiter.limit("30/minute")
|
|
async def getContracts(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteeContract]:
|
|
"""Get all contracts with optional pagination."""
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllContracts(paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/contracts/{contractId}", response_model=TrusteeContract)
|
|
@limiter.limit("30/minute")
|
|
async def getContract(
|
|
request: Request,
|
|
contractId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeContract:
|
|
"""Get a single contract by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
contract = interface.getContract(contractId)
|
|
if not contract:
|
|
raise HTTPException(status_code=404, detail=f"Contract {contractId} not found")
|
|
return contract
|
|
|
|
|
|
@router.get("/contracts/organisation/{orgId}", response_model=List[TrusteeContract])
|
|
@limiter.limit("30/minute")
|
|
async def getContractsByOrganisation(
|
|
request: Request,
|
|
orgId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteeContract]:
|
|
"""Get all contracts for an organisation."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getContractsByOrganisation(orgId)
|
|
|
|
|
|
@router.post("/contracts", response_model=TrusteeContract, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createContract(
|
|
request: Request,
|
|
data: TrusteeContract = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeContract:
|
|
"""Create a new contract."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createContract(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create contract")
|
|
return result
|
|
|
|
|
|
@router.put("/contracts/{contractId}", response_model=TrusteeContract)
|
|
@limiter.limit("10/minute")
|
|
async def updateContract(
|
|
request: Request,
|
|
contractId: str = Path(...),
|
|
data: TrusteeContract = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeContract:
|
|
"""Update a contract (organisationId is immutable)."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getContract(contractId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Contract {contractId} not found")
|
|
|
|
result = interface.updateContract(contractId, data.model_dump(exclude={"id"}))
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to update contract (organisationId cannot be changed)")
|
|
return result
|
|
|
|
|
|
@router.delete("/contracts/{contractId}")
|
|
@limiter.limit("10/minute")
|
|
async def deleteContract(
|
|
request: Request,
|
|
contractId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a contract."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getContract(contractId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Contract {contractId} not found")
|
|
|
|
success = interface.deleteContract(contractId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete contract")
|
|
return {"message": f"Contract {contractId} deleted"}
|
|
|
|
|
|
# ===== Document Routes =====
|
|
|
|
@router.get("/documents", response_model=PaginatedResponse[TrusteeDocument])
|
|
@limiter.limit("30/minute")
|
|
async def getDocuments(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteeDocument]:
|
|
"""Get all documents (metadata only) with optional pagination."""
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllDocuments(paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/documents/{documentId}", response_model=TrusteeDocument)
|
|
@limiter.limit("30/minute")
|
|
async def getDocument(
|
|
request: Request,
|
|
documentId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeDocument:
|
|
"""Get document metadata by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
doc = interface.getDocument(documentId)
|
|
if not doc:
|
|
raise HTTPException(status_code=404, detail=f"Document {documentId} not found")
|
|
return doc
|
|
|
|
|
|
@router.get("/documents/{documentId}/data")
|
|
@limiter.limit("10/minute")
|
|
async def getDocumentData(
|
|
request: Request,
|
|
documentId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
):
|
|
"""Download document binary data."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
doc = interface.getDocument(documentId)
|
|
if not doc:
|
|
raise HTTPException(status_code=404, detail=f"Document {documentId} not found")
|
|
|
|
data = interface.getDocumentData(documentId)
|
|
if not data:
|
|
raise HTTPException(status_code=404, detail="Document data not found")
|
|
|
|
return StreamingResponse(
|
|
io.BytesIO(data),
|
|
media_type=doc.documentMimeType or "application/octet-stream",
|
|
headers={"Content-Disposition": f"attachment; filename={doc.documentName or 'document'}"}
|
|
)
|
|
|
|
|
|
@router.get("/documents/contract/{contractId}", response_model=List[TrusteeDocument])
|
|
@limiter.limit("30/minute")
|
|
async def getDocumentsByContract(
|
|
request: Request,
|
|
contractId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteeDocument]:
|
|
"""Get all documents for a contract."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getDocumentsByContract(contractId)
|
|
|
|
|
|
@router.post("/documents", response_model=TrusteeDocument, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createDocument(
|
|
request: Request,
|
|
data: TrusteeDocument = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeDocument:
|
|
"""Create a new document."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createDocument(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create document")
|
|
return result
|
|
|
|
|
|
@router.put("/documents/{documentId}", response_model=TrusteeDocument)
|
|
@limiter.limit("10/minute")
|
|
async def updateDocument(
|
|
request: Request,
|
|
documentId: str = Path(...),
|
|
data: TrusteeDocument = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteeDocument:
|
|
"""Update document metadata."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getDocument(documentId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Document {documentId} not found")
|
|
|
|
result = interface.updateDocument(documentId, data.model_dump(exclude={"id"}))
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to update document")
|
|
return result
|
|
|
|
|
|
@router.delete("/documents/{documentId}")
|
|
@limiter.limit("10/minute")
|
|
async def deleteDocument(
|
|
request: Request,
|
|
documentId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a document."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getDocument(documentId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Document {documentId} not found")
|
|
|
|
success = interface.deleteDocument(documentId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete document")
|
|
return {"message": f"Document {documentId} deleted"}
|
|
|
|
|
|
# ===== Position Routes =====
|
|
|
|
@router.get("/positions", response_model=PaginatedResponse[TrusteePosition])
|
|
@limiter.limit("30/minute")
|
|
async def getPositions(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteePosition]:
|
|
"""Get all positions with optional pagination."""
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllPositions(paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/positions/{positionId}", response_model=TrusteePosition)
|
|
@limiter.limit("30/minute")
|
|
async def getPosition(
|
|
request: Request,
|
|
positionId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteePosition:
|
|
"""Get a single position by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
position = interface.getPosition(positionId)
|
|
if not position:
|
|
raise HTTPException(status_code=404, detail=f"Position {positionId} not found")
|
|
return position
|
|
|
|
|
|
@router.get("/positions/contract/{contractId}", response_model=List[TrusteePosition])
|
|
@limiter.limit("30/minute")
|
|
async def getPositionsByContract(
|
|
request: Request,
|
|
contractId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteePosition]:
|
|
"""Get all positions for a contract."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getPositionsByContract(contractId)
|
|
|
|
|
|
@router.get("/positions/organisation/{orgId}", response_model=List[TrusteePosition])
|
|
@limiter.limit("30/minute")
|
|
async def getPositionsByOrganisation(
|
|
request: Request,
|
|
orgId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteePosition]:
|
|
"""Get all positions for an organisation."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getPositionsByOrganisation(orgId)
|
|
|
|
|
|
@router.post("/positions", response_model=TrusteePosition, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createPosition(
|
|
request: Request,
|
|
data: TrusteePosition = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteePosition:
|
|
"""Create a new position."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createPosition(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create position")
|
|
return result
|
|
|
|
|
|
@router.put("/positions/{positionId}", response_model=TrusteePosition)
|
|
@limiter.limit("10/minute")
|
|
async def updatePosition(
|
|
request: Request,
|
|
positionId: str = Path(...),
|
|
data: TrusteePosition = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteePosition:
|
|
"""Update a position."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getPosition(positionId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Position {positionId} not found")
|
|
|
|
result = interface.updatePosition(positionId, data.model_dump(exclude={"id"}))
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to update position")
|
|
return result
|
|
|
|
|
|
@router.delete("/positions/{positionId}")
|
|
@limiter.limit("10/minute")
|
|
async def deletePosition(
|
|
request: Request,
|
|
positionId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a position."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getPosition(positionId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Position {positionId} not found")
|
|
|
|
success = interface.deletePosition(positionId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete position")
|
|
return {"message": f"Position {positionId} deleted"}
|
|
|
|
|
|
# ===== Position-Document Link Routes =====
|
|
|
|
@router.get("/position-documents", response_model=PaginatedResponse[TrusteePositionDocument])
|
|
@limiter.limit("30/minute")
|
|
async def getPositionDocuments(
|
|
request: Request,
|
|
pagination: Optional[str] = Query(None),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> PaginatedResponse[TrusteePositionDocument]:
|
|
"""Get all position-document links with optional pagination."""
|
|
paginationParams = _parsePagination(pagination)
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.getAllPositionDocuments(paginationParams)
|
|
|
|
if paginationParams:
|
|
return PaginatedResponse(
|
|
items=result.items,
|
|
pagination=PaginationMetadata(
|
|
currentPage=paginationParams.page or 1,
|
|
pageSize=paginationParams.pageSize or 20,
|
|
totalItems=result.totalItems,
|
|
totalPages=result.totalPages,
|
|
sort=paginationParams.sort if paginationParams else [],
|
|
filters=paginationParams.filters if paginationParams else None
|
|
)
|
|
)
|
|
return PaginatedResponse(items=result.items, pagination=None)
|
|
|
|
|
|
@router.get("/position-documents/{linkId}", response_model=TrusteePositionDocument)
|
|
@limiter.limit("30/minute")
|
|
async def getPositionDocument(
|
|
request: Request,
|
|
linkId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteePositionDocument:
|
|
"""Get a single position-document link by ID."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
link = interface.getPositionDocument(linkId)
|
|
if not link:
|
|
raise HTTPException(status_code=404, detail=f"Link {linkId} not found")
|
|
return link
|
|
|
|
|
|
@router.get("/position-documents/position/{positionId}", response_model=List[TrusteePositionDocument])
|
|
@limiter.limit("30/minute")
|
|
async def getDocumentsForPosition(
|
|
request: Request,
|
|
positionId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteePositionDocument]:
|
|
"""Get all document links for a position."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getDocumentsForPosition(positionId)
|
|
|
|
|
|
@router.get("/position-documents/document/{documentId}", response_model=List[TrusteePositionDocument])
|
|
@limiter.limit("30/minute")
|
|
async def getPositionsForDocument(
|
|
request: Request,
|
|
documentId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> List[TrusteePositionDocument]:
|
|
"""Get all position links for a document."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
return interface.getPositionsForDocument(documentId)
|
|
|
|
|
|
@router.post("/position-documents", response_model=TrusteePositionDocument, status_code=201)
|
|
@limiter.limit("10/minute")
|
|
async def createPositionDocument(
|
|
request: Request,
|
|
data: TrusteePositionDocument = Body(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> TrusteePositionDocument:
|
|
"""Create a new position-document link."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
result = interface.createPositionDocument(data.model_dump())
|
|
if not result:
|
|
raise HTTPException(status_code=400, detail="Failed to create link")
|
|
return result
|
|
|
|
|
|
@router.delete("/position-documents/{linkId}")
|
|
@limiter.limit("10/minute")
|
|
async def deletePositionDocument(
|
|
request: Request,
|
|
linkId: str = Path(...),
|
|
context: RequestContext = Depends(getRequestContext)
|
|
) -> Dict[str, Any]:
|
|
"""Delete a position-document link."""
|
|
interface = getInterface(context.user, mandateId=context.mandateId)
|
|
existing = interface.getPositionDocument(linkId)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail=f"Link {linkId} not found")
|
|
|
|
success = interface.deletePositionDocument(linkId)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to delete link")
|
|
return {"message": f"Link {linkId} deleted"}
|