urmi-ai/backend/app/routes_meta.py
2026-10-08 07:20:18 +05:30

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)