meta options

This commit is contained in:
debu-2 2026-10-08 07:20:18 +05:30
parent 4697dab339
commit 8726c931fa
21 changed files with 3028 additions and 3 deletions

281
backend/app/routes_meta.py Normal file
View file

@ -0,0 +1,281 @@
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)