"""Handlers for custom reports.""" from fastapi import APIRouter from fastapi import Depends from fastapi import Header from fastapi import HTTPException from moneyhub import models from moneyhub.constants.constants import JWTKeys from moneyhub.constants.error import FORBIDDEN_REPORT_URL_ACCESS from moneyhub.constants.error import MISSING_PROFILE_HEADERS from moneyhub.logic import report_custom as logic from moneyhub.schemas.report_custom import ReportCustomCreateSchema from moneyhub.schemas.report_custom import ReportCustomDetailSchema from moneyhub.schemas.report_custom import ReportCustomUpdateSchema from moneyhub.utils.request import extract_json_body from moneyhub.utils.request import extract_token from moneyhub.utils.request import profile_has_access_to_resource report_custom_router = APIRouter(prefix='/reports/custom', tags=['report_custom']) def _verify_report_access( orchard_profile_type: str, orchard_profile_id: str, report_custom_id: int ) -> None: """Verify that the user has access to the report. Args: orchard_profile_type (str): Profile type of the user making the request orchard_profile_id (str): Profile ID of the user making the request report_custom_id (int): ID of the report to check """ if orchard_profile_type is None or orchard_profile_id is None: raise HTTPException(status_code=400, detail=MISSING_PROFILE_HEADERS) custom_report = models.ReportCustom.get_by_id_or_error(report_custom_id) has_access = profile_has_access_to_resource( orchard_profile_type, int(orchard_profile_id), custom_report.account_id, custom_report.subaccount_id ) if not has_access: raise HTTPException(status_code=403, detail=FORBIDDEN_REPORT_URL_ACCESS) @report_custom_router.post( '/account/{account_id}', status_code=200 ) def create_report( account_id: int, payload: ReportCustomCreateSchema, correlation_id: str | None = Header(None), profile_dict: dict = Depends(extract_token) ) -> ReportCustomDetailSchema: """Create a new custom report. Args: account_id (int): ID of the account creating the report payload (ReportCustomCreateSchema): JSON body payload correlation_id (str): The correlation ID profile_dict (dict): Profile details Returns: ReportCustomDetailSchema: The created report """ orchard_identity_id = profile_dict[JWTKeys.ORCHARD_IDENTITY_ID] return logic.create_report( account_id, payload, orchard_identity_id, correlation_id, ) @report_custom_router.get( '/{report_custom_id}', status_code=200 ) def get_custom_report( report_custom_id: int, orchard_profile_id: str | None = Header(None), orchard_profile_type: str | None = Header(None) ) -> ReportCustomDetailSchema: """Get a custom report by ID. Args: report_custom_id (int): ID of the report orchard_profile_id (str | None): Profile Identifier orchard_profile_type (str | None): Type of profile Returns: ReportCustomDetailSchema: custom report """ _verify_report_access(orchard_profile_type, orchard_profile_id, report_custom_id) return logic.get_custom_report(report_custom_id) @report_custom_router.put( '/{report_custom_id}', status_code=200 ) def update_custom_report( report_custom_id: int, payload: ReportCustomUpdateSchema, orchard_profile_id: str | None = Header(None), orchard_profile_type: str | None = Header(None) ) -> ReportCustomDetailSchema: """Update a custom report by ID. Args: report_custom_id (int): ID of the report payload (ReportCustomUpdateSchema): JSON body payload orchard_profile_id (str): Profile Identifier orchard_profile_type (str): Type of profile Returns: ReportCustomDetailSchema: Updated custom report """ _verify_report_access(orchard_profile_type, orchard_profile_id, report_custom_id) report_data = extract_json_body(payload) return logic.update_custom_report(report_custom_id, report_data) @report_custom_router.delete( '/{report_custom_id}', status_code=204 ) def delete_custom_report( report_custom_id: int, orchard_profile_id: str | None = Header(None), orchard_profile_type: str | None = Header(None) ) -> None: """Delete a custom report by ID. Args: report_custom_id (int): ID of the report orchard_profile_id (str): Profile Identifier orchard_profile_type (str): Type of profile """ _verify_report_access(orchard_profile_type, orchard_profile_id, report_custom_id) logic.delete_custom_report(report_custom_id) @report_custom_router.get( '/account/{account_id}', status_code=200 ) def get_custom_reports( account_id: int, contract_id: int | None = None, subaccount_id: int | None = None, is_subaccount: bool = False ) -> list[ReportCustomDetailSchema]: """Get custom reports by an account_id. Args: account_id (int): ID of the account contract_id (int): Contract to filter by subaccount_id (int): Optional subaccount to filter by is_subaccount (bool): Whether to filter by subaccount_id Returns: list: list of custom reports """ return logic.get_custom_reports(account_id, contract_id, subaccount_id, is_subaccount) @report_custom_router.get('/{report_custom_id}/download', status_code=200) def get_report_presigned_url( report_custom_id: int, orchard_profile_id: str | None = Header(None), orchard_profile_type: str | None = Header(None) ) -> dict: """Get presigned S3 URL for downloading report. Args: report_custom_id (int): The id of custom report orchard_profile_id (str): Profile Identifier orchard_profile_type (str): Type of profile Returns: dict: presigned url of report file location """ _verify_report_access(orchard_profile_type, orchard_profile_id, report_custom_id) return {'url': logic.get_report_presigned_url(report_custom_id)} @report_custom_router.post( '/statement-period/{statement_period_id}/regenerate', status_code=200 ) def regenerate_custom_reports( statement_period_id: int, correlation_id: str | None = Header(None) ) -> list[ReportCustomDetailSchema]: """Regenerate custom reports. Args: statement_period_id (int): ID of the statement period to regenerate reports for correlation_id (str): The correlation ID of the request Returns: list: reports to regenerate """ return logic.regenerate_custom_reports(statement_period_id, correlation_id)