281 lines
8.1 KiB
Python
281 lines
8.1 KiB
Python
from urllib.parse import quote
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from fastapi.responses import RedirectResponse
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .auth import current_user
|
|
from .config import settings
|
|
from .database import get_db
|
|
from .meta_service import (
|
|
MetaApiError,
|
|
analytics,
|
|
authorization_url,
|
|
disconnect,
|
|
finish_oauth,
|
|
http_meta,
|
|
list_ad_accounts,
|
|
list_ads,
|
|
list_businesses,
|
|
list_campaigns,
|
|
list_pages,
|
|
overview,
|
|
page_detail,
|
|
public_connection,
|
|
publish_campaign,
|
|
load_connection,
|
|
recent_audit,
|
|
select_ad_account,
|
|
select_page,
|
|
set_ad_status,
|
|
set_campaign_status,
|
|
settings_view,
|
|
update_ad,
|
|
update_campaign,
|
|
)
|
|
from .models import UserAccount
|
|
|
|
router = APIRouter(prefix="/api/v1/meta", tags=["meta"])
|
|
|
|
|
|
class Confirm(BaseModel):
|
|
confirm: bool = False
|
|
|
|
|
|
class IdBody(Confirm):
|
|
id: str = Field(min_length=1, max_length=64)
|
|
|
|
|
|
class StatusBody(Confirm):
|
|
status: str
|
|
|
|
|
|
class CampaignEdit(Confirm):
|
|
name: str | None = None
|
|
daily_budget: float | None = Field(default=None, gt=0, le=10_000_000)
|
|
|
|
|
|
class CampaignPublish(Confirm):
|
|
name: str = Field(min_length=1, max_length=120)
|
|
objective: str
|
|
destination: str
|
|
page_id: str
|
|
daily_budget: float = Field(gt=0, le=10_000_000)
|
|
countries: list[str] = Field(min_length=1, max_length=5)
|
|
age_min: int = Field(default=18, ge=18, le=65)
|
|
age_max: int = Field(default=65, ge=18, le=65)
|
|
message: str = Field(min_length=1, max_length=2000)
|
|
headline: str = ""
|
|
link: str = ""
|
|
call_to_action: str = "LEARN_MORE"
|
|
activate: bool = False
|
|
|
|
|
|
class AdEdit(Confirm):
|
|
name: str | None = None
|
|
message: str | None = None
|
|
headline: str | None = None
|
|
call_to_action: str | None = None
|
|
link: str | None = None
|
|
|
|
|
|
def _confirmed(body: Confirm) -> None:
|
|
if not body.confirm:
|
|
raise http_meta(MetaApiError(422, "validation", "Confirm this action before Urmi sends it to Meta."))
|
|
|
|
|
|
def _redirect(query: str) -> RedirectResponse:
|
|
origin = settings.frontend_origin.rstrip("/")
|
|
return RedirectResponse(f"{origin}/console/meta/connections?{query}", status_code=302)
|
|
|
|
|
|
@router.get("/status")
|
|
def status(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
conn = load_connection(db, user)
|
|
visible = conn if conn and conn.status != "disconnected" else None
|
|
return public_connection(visible)
|
|
|
|
|
|
@router.get("/oauth/start")
|
|
def oauth_start(user: UserAccount = Depends(current_user)) -> dict:
|
|
try:
|
|
return {"authorization_url": authorization_url(user)}
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/oauth/callback")
|
|
async def oauth_callback(code: str = "", state: str = "", error: str = "", db: Session = Depends(get_db)):
|
|
if error or not code or not state:
|
|
return _redirect("error=Meta%20Login%20was%20cancelled")
|
|
try:
|
|
await finish_oauth(db, code, state)
|
|
except MetaApiError as exc:
|
|
return _redirect(f"error={quote(exc.message)}")
|
|
except Exception:
|
|
return _redirect("error=Meta%20Login%20could%20not%20be%20completed")
|
|
return _redirect("connected=1")
|
|
|
|
|
|
@router.post("/disconnect")
|
|
def disconnect_meta(body: Confirm, user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
disconnect(db, user)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
return {"status": "disconnected"}
|
|
|
|
|
|
@router.get("/overview")
|
|
async def meta_overview(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
return await overview(db, user)
|
|
|
|
|
|
@router.get("/businesses")
|
|
async def businesses(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return await list_businesses(db, user)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/pages")
|
|
async def pages(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return await list_pages(db, user)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/pages/{page_id}")
|
|
async def page(page_id: str, user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return await page_detail(db, user, page_id)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/pages/select")
|
|
async def choose_page(body: IdBody, user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await select_page(db, user, body.id)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/ad-accounts")
|
|
async def ad_accounts(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return await list_ad_accounts(db, user)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/ad-accounts/select")
|
|
async def choose_ad_account(body: IdBody, user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await select_ad_account(db, user, body.id)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/campaigns")
|
|
async def campaigns(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return await list_campaigns(db, user)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/campaigns")
|
|
async def create_campaign(body: CampaignPublish, user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await publish_campaign(db, user, body.model_dump())
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/campaigns/{campaign_id}")
|
|
async def edit_campaign(
|
|
campaign_id: str,
|
|
body: CampaignEdit,
|
|
user: UserAccount = Depends(current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await update_campaign(db, user, campaign_id, body.name, body.daily_budget)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/campaigns/{campaign_id}/status")
|
|
async def campaign_status(
|
|
campaign_id: str,
|
|
body: StatusBody,
|
|
user: UserAccount = Depends(current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await set_campaign_status(db, user, campaign_id, body.status)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/ads")
|
|
async def ads(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return await list_ads(db, user)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/ads/{ad_id}")
|
|
async def edit_ad(ad_id: str, body: AdEdit, user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await update_ad(db, user, ad_id, body.model_dump())
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.post("/ads/{ad_id}/status")
|
|
async def ad_status(
|
|
ad_id: str,
|
|
body: StatusBody,
|
|
user: UserAccount = Depends(current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> dict:
|
|
_confirmed(body)
|
|
try:
|
|
return await set_ad_status(db, user, ad_id, body.status)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/analytics")
|
|
async def meta_analytics(
|
|
date_preset: str = "last_30d",
|
|
user: UserAccount = Depends(current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> dict:
|
|
try:
|
|
return await analytics(db, user, date_preset)
|
|
except MetaApiError as exc:
|
|
raise http_meta(exc) from exc
|
|
|
|
|
|
@router.get("/audit")
|
|
def audit(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
return {"events": recent_audit(db, user)}
|
|
|
|
|
|
@router.get("/settings")
|
|
def meta_settings(user: UserAccount = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
|
return settings_view(db, user)
|