feat(telegram_bot): added campaign detail view and deletion

This commit is contained in:
ITQ
2025-02-20 22:13:41 +03:00
parent 8d7ff22d7a
commit 90c6c7c045
4 changed files with 181 additions and 5 deletions
@@ -4,3 +4,4 @@
AIOGRAM_BOT_TOKEN= AIOGRAM_BOT_TOKEN=
AIOGRAM_BACKEND_ADDRESS=http://localhost:8080 AIOGRAM_BACKEND_ADDRESS=http://localhost:8080
REDIS_URI=redis://localhost:6379 REDIS_URI=redis://localhost:6379
MINIO_ENDPOINT=localhost:9000
+2
View File
@@ -12,3 +12,5 @@ BOT_TOKEN = os.getenv("AIOGRAM_BOT_TOKEN", None)
API_ENDPOINT = os.getenv("AIOGRAM_BACKEND_URL", "http://localhost:8080") API_ENDPOINT = os.getenv("AIOGRAM_BACKEND_URL", "http://localhost:8080")
REDIS_URI = os.getenv("REDIS_URI", "redis://localhost:6379") REDIS_URI = os.getenv("REDIS_URI", "redis://localhost:6379")
MINIO_URL = f"http://{os.getenv('MINIO_ENDPOINT', 'localhost:9000')}"
@@ -1,13 +1,23 @@
import tempfile
from http import HTTPStatus as status
from mimetypes import guess_extension
from typing import Any from typing import Any
from urllib.parse import urlparse
import httpx
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery from aiogram.types import CallbackQuery, ContentType
from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog import Dialog, DialogManager, Window
from aiogram_dialog.widgets.kbd import Button, ListGroup, ScrollingGroup from aiogram_dialog.api.entities import MediaAttachment
from aiogram_dialog.widgets.common import Whenable
from aiogram_dialog.widgets.kbd import Button, ListGroup, ScrollingGroup, Start
from aiogram_dialog.widgets.media import DynamicMedia
from aiogram_dialog.widgets.text import Const, Format from aiogram_dialog.widgets.text import Const, Format
import config
from api.client import AdNovaClient from api.client import AdNovaClient
from states.campaigns import CampaignsDailogState from states.campaigns import CampaignsDailogState
from states.campaign import CampaignDialogState
async def campaigns(**kwargs: dict[Any]) -> dict[str, Any]: async def campaigns(**kwargs: dict[Any]) -> dict[str, Any]:
@@ -17,7 +27,11 @@ async def campaigns(**kwargs: dict[Any]) -> dict[str, Any]:
async with AdNovaClient() as client: async with AdNovaClient() as client:
campaigns = await client.list_campaigns(state_data["advertiser_id"]) campaigns = await client.list_campaigns(state_data["advertiser_id"])
campaigns = [campaign.model_dump(mode="json") for campaign in campaigns] campaigns = (
[campaign.model_dump(mode="json") for campaign in campaigns]
if campaigns != []
else [{"campaign_id": ""}]
)
return { return {
"campaigns": campaigns, "campaigns": campaigns,
@@ -27,16 +41,130 @@ async def campaigns(**kwargs: dict[Any]) -> dict[str, Any]:
async def campaign_detail_on_click( async def campaign_detail_on_click(
callback: CallbackQuery, button: Button, manager: DialogManager callback: CallbackQuery, button: Button, manager: DialogManager
) -> None: ) -> None:
manager_data: dict[Any] = await manager.load_data()
state: FSMContext = manager_data["middleware_data"]["state"]
state_data = await state.get_data()
advertiser_id = state_data["advertiser_id"]
campaign_id = manager.item_id
if campaign_id == "":
return
async with AdNovaClient() as client:
campaign = await client.get_campaign(
advertiser_id=advertiser_id,
campaign_id=campaign_id,
)
if campaign.ad_image:
campaign.ad_image = (
f"{config.MINIO_URL}{urlparse(campaign.ad_image).path}"
)
await manager.update({"campaign": campaign.model_dump(mode="json")})
await callback.answer() await callback.answer()
await manager.switch_to(CampaignsDailogState.campaign)
def campaign_has_ad_image(
data: dict, widget: Whenable, manager: DialogManager
) -> bool:
return bool(data["dialog_data"]["campaign"]["ad_image"])
def campaign_has_not_ad_image(
data: dict, widget: Whenable, manager: DialogManager
) -> bool:
return not data["dialog_data"]["campaign"]["ad_image"]
async def campaign_by_id(**kwargs: dict[Any]) -> dict[str, Any]:
manager: DialogManager = kwargs["dialog_manager"]
manager_data = await manager.load_data()
ad_image_url = manager_data["dialog_data"]["campaign"]["ad_image"]
if not ad_image_url:
return {}
async with httpx.AsyncClient() as client:
response = await client.get(ad_image_url)
if response.status_code == status.OK:
content_type = response.headers.get("Content-Type", "image/jpeg")
ext = guess_extension(content_type) or ".jpg"
with tempfile.NamedTemporaryFile(
suffix=ext, delete=False
) as temp_file:
temp_file.write(response.content)
temp_file.flush()
temp_file_path = temp_file.name
attachment = MediaAttachment(
type=ContentType.PHOTO, path=temp_file_path
)
else:
attachment = None
return {"ad_image": attachment}
async def delete_ad_image(
callback: CallbackQuery, button: Button, manager: DialogManager
) -> None:
manager_data = await manager.load_data()
state: FSMContext = manager_data["middleware_data"]["state"]
state_data = await state.get_data()
campaign = manager_data["dialog_data"]["campaign"]
campaign_id = campaign["campaign_id"]
advertiser_id = state_data["advertiser_id"]
async with AdNovaClient() as client:
await client.delete_ad_image(
advertiser_id=advertiser_id, campaign_id=campaign_id
)
campaign["ad_image"] = None
await callback.answer("Campaign image deleted")
async def delete_campaign(
callback: CallbackQuery, button: Button, manager: DialogManager
) -> None:
manager_data = await manager.load_data()
state: FSMContext = manager_data["middleware_data"]["state"]
state_data = await state.get_data()
campaign = manager_data["dialog_data"]["campaign"]
campaign_id = campaign["campaign_id"]
advertiser_id = state_data["advertiser_id"]
async with AdNovaClient() as client:
await client.delete_campaign(
advertiser_id=advertiser_id, campaign_id=campaign_id
)
await callback.answer("Campaign deleted")
await manager.switch_to(CampaignsDailogState.campaigns)
async def back_to_list(
callback: CallbackQuery, button: Button, manager: DialogManager
) -> None:
await manager.switch_to(CampaignsDailogState.campaigns)
campaigns_dialog = Dialog( campaigns_dialog = Dialog(
Window( Window(
Const("Campaigns:"), Const("Campaigns:"),
Button(Const(" Create"), id="create_campaign"),
ScrollingGroup( ScrollingGroup(
ListGroup( ListGroup(
Button( Button(
Format("{item[ad_title]}"), Format("{item[campaign_id]}"),
id="detail", id="detail",
on_click=campaign_detail_on_click, on_click=campaign_detail_on_click,
), ),
@@ -51,4 +179,48 @@ campaigns_dialog = Dialog(
state=CampaignsDailogState.campaigns, state=CampaignsDailogState.campaigns,
getter=campaigns, getter=campaigns,
), ),
Window(
DynamicMedia("ad_image", when=campaign_has_ad_image),
Format("• ID: <code>{dialog_data[campaign][campaign_id]}</code>"),
Format("• Title: {dialog_data[campaign][ad_title]}"),
Format("• Text: {dialog_data[campaign][ad_text]}"),
Format(
"• Impressions limit: {dialog_data[campaign][impressions_limit]}"
),
Format("• Clicks limit: {dialog_data[campaign][clicks_limit]}"),
Format(
"• Cost per impression: "
"{dialog_data[campaign][cost_per_impression]}"
),
Format("• Cost per click: {dialog_data[campaign][cost_per_click]}"),
Format("• Start date: {dialog_data[campaign][start_date]}"),
Format("• End date: {dialog_data[campaign][end_date]}"),
Format("• Targeting"),
Format("\t • Gender: {dialog_data[campaign][targeting][gender]}"),
Format("\t • Age from: {dialog_data[campaign][targeting][age_from]}"),
Format("\t • Age to: {dialog_data[campaign][targeting][age_to]}"),
Format("\t • Location: {dialog_data[campaign][targeting][location]}"),
Button(Const("📝 Edit campaign"), id="edit_campaign"),
Start(
Const("⬆️ Upload image"),
id="upload_ad_image",
state=CampaignDialogState.delete_ad_image,
data=
when=campaign_has_not_ad_image,
),
Button(
Const("🗑️ Delete image"),
id="delete_image",
on_click=delete_ad_image,
when=campaign_has_ad_image,
),
Button(
Const("🗑️ Delete campaign"),
id="delete_ad_image",
on_click=delete_campaign,
),
Button(Const("⬅️ Back to list"), id="back", on_click=back_to_list),
state=CampaignsDailogState.campaign,
getter=campaign_by_id,
),
) )
@@ -1,7 +1,7 @@
from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.state import State, StatesGroup
class CampaignState(StatesGroup): class CampaignDialogState(StatesGroup):
ad_title = State() ad_title = State()
ad_text = State() ad_text = State()
impressions_limit = State() impressions_limit = State()
@@ -14,3 +14,4 @@ class CampaignState(StatesGroup):
age_from = State() age_from = State()
age_to = State() age_to = State()
location = State() location = State()
delete_ad_image = State()