from typing import Any from fastapi import APIRouter, Body, Depends, Path from pyxdi.ext.fastapi import Inject from starlette import status from starlette.responses import Response from campaigns.api import auth, query_params, schemas from campaigns.auth.dtos import User from campaigns.connectors.facebook.models import AudienceSummary from campaigns.meta.dtos import AdFormat, EngagementAudienceEvent from campaigns.meta.handlers import ( CreateCampaignAdImageHandler, CreateCampaignAdImageRequest, CreateCampaignAdVideoHandler, CreateCampaignAdVideoRequest, CreateCampaignEngagementAudienceHandler, CreateCampaignEngagementAudienceRequest, CreateCampaignEngagementAudienceResponse, CreateCampaignHandler, CreateCampaignImageHandler, CreateCampaignImageRequest, CreateCampaignRequest, CreateCampaignVideoHandler, CreateCampaignVideoRequest, CreateOrUpdateCampaignTargetingAudienceHandler, CreateOrUpdateCampaignTargetingAudienceRequest, DeleteCampaignImageHandler, DeleteCampaignImageRequest, DeleteCampaignVideoHandler, DeleteCampaignVideoRequest, GetCampaignAdFormatsHandler, GetCampaignAdFormatsRequest, GetCampaignAdPreviewHandler, GetCampaignAdPreviewRequest, GetCampaignAdPreviewResponse, GetCampaignAudiencesHandler, GetCampaignAudiencesRequest, GetCampaignAudienceSummaryHandler, GetCampaignImageHandler, GetCampaignImageRequest, GetCampaignImagesHandler, GetCampaignImagesRequest, GetCampaignOverviewHandler, GetCampaignOverviewRequest, GetCampaignPostHandler, GetCampaignPostRequest, GetCampaignPostsHandler, GetCampaignPostsRequest, GetCampaignVideoHandler, GetCampaignVideoRequest, GetCampaignVideosHandler, GetCampaignVideosRequest, GetEngagementAudienceEventsHandler, GetEngagementAudienceEventsRequest, RefreshCampaignAdImageHandler, RefreshCampaignAdImageRequest, RefreshCampaignAdVideoHandler, RefreshCampaignAdVideoRequest, SubmitCampaignHandler, SubmitCampaignRequest, SubmitCampaignResponse, UpdateCampaignCreativeHandler, UpdateCampaignCreativeRequest, UpdateCampaignHandler, UpdateCampaignRequest, UpdateCampaignScheduleAndBudgetHandler, UpdateCampaignScheduleAndBudgetRequest, ) from campaigns.meta.models import ( Campaign, CampaignImage, CampaignVideo, TargetingAudience, ) router = APIRouter(prefix="/meta", tags=["meta"]) @router.post( "/campaigns", operation_id="createMetaCampaign", response_model=schemas.MetaCampaign, response_model_by_alias=True, status_code=status.HTTP_201_CREATED, ) async def create_campaign( user: User = Depends(auth.get_user), data: schemas.MetaCampaignInput = Body(), handler: CreateCampaignHandler = Inject(), ) -> Campaign: return await handler.handle( CreateCampaignRequest( user=user, name=data.name, vendor_id=data.vendor_id, subaccount_id=data.subaccount_id or 0, global_participant_id=data.global_participant_id, product_id=data.product_id, facebook_page_id=data.facebook_page_id, objective=data.objective, ) ) @router.get( "/campaigns/{campaignId}", operation_id="getMetaCampaignOverview", response_model=schemas.MetaCampaignOverview, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_overview( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetCampaignOverviewHandler = Inject(), ) -> Any: response = await handler.handle( GetCampaignOverviewRequest(user=user, campaign_id=campaign_id) ) return { "campaign": response.campaign, "summary": response.summary, } @router.put( "/campaigns/{campaignId}", operation_id="updateMetaCampaign", response_model=schemas.MetaCampaign, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def update_campaign( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.MetaCampaignInput = Body(), handler: UpdateCampaignHandler = Inject(), ) -> Campaign: return await handler.handle( UpdateCampaignRequest( user=user, campaign_id=campaign_id, name=data.name, vendor_id=data.vendor_id, subaccount_id=data.subaccount_id or 0, global_participant_id=data.global_participant_id, product_id=data.product_id, facebook_page_id=data.facebook_page_id, objective=data.objective, ), ) @router.put( "/campaigns/{campaignId}/schedule-and-budget", operation_id="updateMetaCampaignScheduleAndBudget", response_model=schemas.MetaCampaign, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def update_campaign_schedule_and_budget( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.MetaCampaignScheduleAndBudgetInput = Body(), handler: UpdateCampaignScheduleAndBudgetHandler = Inject(), ) -> Campaign: return await handler.handle( UpdateCampaignScheduleAndBudgetRequest( user=user, campaign_id=campaign_id, start_time=data.start_time, end_time=data.end_time, lifetime_budget=data.lifetime_budget, ) ) @router.put( "/campaigns/{campaignId}/creative", operation_id="updateMetaCampaignCreative", response_model=schemas.MetaCampaign, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def update_campaign_creative( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.MetaCampaignCreativeInput = Body(), handler: UpdateCampaignCreativeHandler = Inject(), ) -> Campaign: return await handler.handle( UpdateCampaignCreativeRequest( user=user, campaign_id=campaign_id, content_type=data.content_type, platforms=data.platform.to_list(), post_external_id=data.post_external_id, primary_text=data.primary_text, headline=data.headline, description=data.description, link_type=data.link_type, website_url=data.website_url, landing_page_path=data.landing_page_path, landing_page_product_id=data.landing_page_product_id, call_to_action=data.call_to_action, ) ) @router.get( "/campaigns/{campaignId}/audiences", operation_id="getMetaCampaignAudiences", response_model=schemas.MetaCampaignAudiences, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_audiences( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetCampaignAudiencesHandler = Inject(), ) -> dict[str, Any]: response = await handler.handle( GetCampaignAudiencesRequest(user=user, campaign_id=campaign_id) ) return { "targetingAudience": response.targeting_audience, } @router.post( "/campaigns/{campaignId}/targeting-audience", operation_id="createOrUpdateMetaCampaignTargetingAudience", response_model=schemas.MetaTargetingAudience, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def create_or_update_campaign_targeting_audience( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.MetaTargetingAudienceInput = Body(), handler: CreateOrUpdateCampaignTargetingAudienceHandler = Inject(), ) -> TargetingAudience: return await handler.handle( CreateOrUpdateCampaignTargetingAudienceRequest( user=user, campaign_id=campaign_id, locations=[location.dict() for location in data.locations], age_min=data.age_min, age_max=data.age_max, genders=data.gender.to_list(), interests=[interest.dict() for interest in data.interests], ) ) @router.get( "/campaigns/{campaignId}/images", operation_id="getMetaCampaignImages", response_model=list[schemas.MetaCampaignImage], response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, deprecated=True, ) async def get_campaign_images( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetCampaignImagesHandler = Inject(), ) -> list[CampaignImage]: return await handler.handle( GetCampaignImagesRequest(user=user, campaign_id=campaign_id) ) @router.post( "/campaigns/{campaignId}/images", operation_id="createMetaCampaignImage", status_code=status.HTTP_201_CREATED, response_model=schemas.MetaCampaignImageCredentials, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def create_campaign_image( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.CreateMetaImageInput = Body(), handler: CreateCampaignImageHandler = Inject(), ) -> dict[str, Any]: image, credentials = await handler.handle( CreateCampaignImageRequest( user=user, campaign_id=campaign_id, filename=data.filename, position_group=data.position_group, ), ) return {"imageId": image.id, "credentials": credentials} @router.get( "/campaigns/{campaignId}/images/{imageId}", operation_id="getMetaCampaignImage", response_model=schemas.MetaCampaignImage, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_image( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), image_id: str = Path(alias="imageId"), handler: GetCampaignImageHandler = Inject(), ) -> CampaignImage: return await handler.handle( GetCampaignImageRequest(user=user, campaign_id=campaign_id, image_id=image_id), ) @router.delete( "/campaigns/{campaignId}/images/{imageId}", operation_id="deleteMetaCampaignImage", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def delete_campaign_image( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), image_id: str = Path(alias="imageId"), handler: DeleteCampaignImageHandler = Inject(), ) -> None: return await handler.handle( DeleteCampaignImageRequest( user=user, campaign_id=campaign_id, image_id=image_id ), ) @router.get( "/campaigns/{campaignId}/videos", operation_id="getMetaCampaignVideos", response_model=list[schemas.MetaCampaignVideo], response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, deprecated=True, ) async def get_campaign_videos( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetCampaignVideosHandler = Inject(), ) -> list[CampaignVideo]: return await handler.handle( GetCampaignVideosRequest(user=user, campaign_id=campaign_id) ) @router.post( "/campaigns/{campaignId}/videos", operation_id="createMetaCampaignVideo", status_code=status.HTTP_201_CREATED, response_model=schemas.MetaCampaignVideoCredentials, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def create_campaign_video( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.CreateMetaVideoInput = Body(), handler: CreateCampaignVideoHandler = Inject(), ) -> dict[str, Any]: video, credentials = await handler.handle( CreateCampaignVideoRequest( user=user, campaign_id=campaign_id, filename=data.filename, position_group=data.position_group, ) ) return { "videoId": video.id, "credentials": credentials, } @router.get( "/campaigns/{campaignId}/videos/{videoId}", operation_id="getMetaCampaignVideo", response_model=schemas.MetaCampaignVideo, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_video( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), video_id: str = Path(alias="videoId"), handler: GetCampaignVideoHandler = Inject(), ) -> CampaignVideo: return await handler.handle( GetCampaignVideoRequest( user=user, campaign_id=campaign_id, video_id=video_id, ) ) @router.delete( "/campaigns/{campaignId}/videos/{videoId}", operation_id="deleteMetaCampaignVideo", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def delete_campaign_video( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), video_id: str = Path(alias="videoId"), handler: DeleteCampaignVideoHandler = Inject(), ) -> None: return await handler.handle( DeleteCampaignVideoRequest( user=user, campaign_id=campaign_id, video_id=video_id, ) ) @router.get( "/campaigns/{campaignId}/adformats", operation_id="getMetaCampaignAdFormats", response_model=list[schemas.MetaCampaignAdFormat], response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_ad_formats( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), params: query_params.MetaCampaignAdFormatsQueryParams = Depends(), handler: GetCampaignAdFormatsHandler = Inject(), ) -> list[AdFormat]: return await handler.handle( GetCampaignAdFormatsRequest( user=user, campaign_id=campaign_id, content_type=params.content_type, platforms=params.platform.to_list(), ) ) @router.get( "/campaigns/{campaignId}/adpreview", operation_id="getMetaCampaignAdPreview", response_model=schemas.MetaCampaignAdPreview, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_ad_preview( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), params: query_params.MetaCampaignAdPreviewInputQueryParams = Depends(), handler: GetCampaignAdPreviewHandler = Inject(), ) -> GetCampaignAdPreviewResponse: return await handler.handle( GetCampaignAdPreviewRequest( user=user, campaign_id=campaign_id, content_type=params.content_type, post_external_id=params.post_external_id, primary_text=params.primary_text, headline=params.headline, description=params.description, website_url=params.website_url, call_to_action=params.call_to_action, ad_format_id=params.ad_format_id, ) ) @router.post( "/campaigns/{campaignId}/audience-summary", operation_id="getMetaCampaignAudienceSummary", response_model=schemas.MetaCampaignAudienceSummary, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_audience_summary( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.MetaAudienceSummaryInput = Body(), handler: GetCampaignAudienceSummaryHandler = Inject(), ) -> AudienceSummary: return await handler.handle(data.to_request(user=user, campaign_id=campaign_id)) @router.get( "/campaigns/{campaignId}/posts", operation_id="getMetaCampaignPosts", summary="Get Meta campaign available posts for selection", response_model=list[schemas.MetaCampaignPost], response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_posts( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetCampaignPostsHandler = Inject(), ) -> Any: return await handler.handle( GetCampaignPostsRequest( user=user, campaign_id=campaign_id, ) ) @router.get( "/campaigns/{campaignId}/post", operation_id="getMetaCampaignPost", summary="Get Meta campaign current post", response_model=schemas.MetaCampaignPostResponse, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_post( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetCampaignPostHandler = Inject(), ) -> Any: post = await handler.handle( GetCampaignPostRequest( user=user, campaign_id=campaign_id, ) ) return {"post": post} # Internal endpoints @router.post( "/campaigns/images/{imageId}/adimage", operation_id="createMetaCampaignAdImage", tags=["internal"], status_code=status.HTTP_201_CREATED, response_model=schemas.MetaCampaignImage, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def create_campaign_ad_image( image_id: str = Path(alias="imageId"), data: schemas.CreateMetaAdImageInput = Body(), handler: CreateCampaignAdImageHandler = Inject(), ) -> CampaignImage: return await handler.handle( CreateCampaignAdImageRequest( image_id=image_id, image_url=data.url, ) ) @router.put( "/campaigns/images/{imageId}/adimage", operation_id="refreshMetaCampaignAdImage", tags=["internal"], response_model=schemas.MetaCampaignImage, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def refresh_campaign_ad_image( image_id: str = Path(alias="imageId"), handler: RefreshCampaignAdImageHandler = Inject(), ) -> CampaignImage: return await handler.handle(RefreshCampaignAdImageRequest(image_id=image_id)) @router.post( "/campaigns/videos/{videoId}/advideo", operation_id="createMetaCampaignAdVideo", tags=["internal"], status_code=status.HTTP_201_CREATED, response_model=schemas.MetaCampaignVideo, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def create_campaign_ad_video( video_id: str = Path(alias="videoId"), data: schemas.CreateMetaAdVideoInput = Body(), handler: CreateCampaignAdVideoHandler = Inject(), ) -> CampaignVideo: return await handler.handle( CreateCampaignAdVideoRequest( video_id=video_id, video_url=data.url, ) ) @router.put( "/campaigns/videos/{videoId}/advideo", operation_id="refreshMetaCampaignAdVideo", tags=["internal"], response_model=schemas.MetaCampaignVideo, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def refresh_campaign_ad_video( video_id: str = Path(alias="videoId"), handler: RefreshCampaignAdVideoHandler = Inject(), ) -> CampaignVideo: return await handler.handle(RefreshCampaignAdVideoRequest(video_id=video_id)) @router.get( "/campaigns/{campaignId}/engagement-audience-events", operation_id="getMetaCampaignEngagementAudienceEvents", response_model=list[schemas.MetaEngagementAudienceEvent], response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def get_campaign_engagement_audience_events( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: GetEngagementAudienceEventsHandler = Inject(), ) -> list[EngagementAudienceEvent]: return await handler.handle( GetEngagementAudienceEventsRequest( user=user, campaign_id=campaign_id, ) ) @router.post( "/campaigns/{campaignId}/engagement-audience", operation_id="createMetaCampaignEngagementAudience", response_model=schemas.MetaCampaignEngagementAudience, response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def create_campaign_engagement_audience( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), data: schemas.CreateMetaCampaignEngagementAudienceInput = Body(), handler: CreateCampaignEngagementAudienceHandler = Inject(), ) -> CreateCampaignEngagementAudienceResponse: return await handler.handle( CreateCampaignEngagementAudienceRequest( user=user, campaign_id=campaign_id, event_ids=data.event_ids, ) ) @router.post( "/campaigns/{campaignId}/submit", operation_id="submitMetaCampaign", response_model=schemas.MetaAdCampaign, # format TBD response_model_by_alias=True, responses={ status.HTTP_404_NOT_FOUND: {"model": schemas.Error}, }, ) async def submit_campaign( user: User = Depends(auth.get_user), campaign_id: str = Path(alias="campaignId"), handler: SubmitCampaignHandler = Inject(), ) -> SubmitCampaignResponse: return await handler.handle( SubmitCampaignRequest( user=user, campaign_id=campaign_id, ) )