cc985e07de
Клиентский модуль «Реклама в Телеграме по своей базе» (робот-в-браузере, МТС Маркетолог). Задача 5.2 — обратная связь по отказу: - миграция client_tg_campaigns.status_reason (varchar 500, nullable; RLS без изменений — колонка на существующей таблице), запись v8.88 в CHANGELOG_schema - Campaign: status_reason в fillable - NotificationService::notifyTelegramCampaignRejected — in-app уведомление всем активным пользователям тенанта без pref-гейта (важное операционное сообщение доходит всегда) - RunTelegramCampaignJob: при провале робота сохраняет причину в status_reason и шлёт уведомление (Tenant грузится внутри tenantTx для RLS, notify — после транзакции) - фронт: telegram.ts (+status_reason), AdvertisingTelegramView показывает причину отказа/ошибки - тесты: RejectNotifyTest (4 Pest) + 2 Vitest на экран Закалка робота против частых глюков кабинета МТС: - browser.js: gotoStable() — терпеливая загрузка с перезагрузкой (кабинет виснет на пустой крутилке) - session.js: isLoggedIn через gotoStable — больше нет ложного «вход слетел» - cabinet.js: знакомство пропускается если его нет (только для новичков); оферта по #isOfferAccepted; осознан шаг /payment (денежная развилка «оплатить/без оплаты» — Сессия 6) Живая разведка кабинета зафиксирована в FLOW-FINDINGS.md (селекторы, шаг /payment, поле файла модератору). Проверки: Pest ClientTg 93/93, Vitest экрана 7/7, node-тесты робота 20/20. Робот в тестах замокан, тесты на liderra_testing. Реальных ПДн нет (номера фейковые 7999…). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
import { apiClient, ensureCsrfCookie } from './client';
|
|
|
|
/**
|
|
* API-модуль клиентской Telegram-рекламы «по своей базе» (план §Сессия 3, задача 3.2).
|
|
*
|
|
* Эндпоинты под [auth:sanctum, tenant], префикс /api/telegram. GET'ы не требуют
|
|
* CSRF-cookie, мутации (POST) — требуют.
|
|
*
|
|
* 🔴 Отличие от СМС-близнеца: создание (create) и запуск (launch) РАЗДЕЛЕНЫ.
|
|
* create возвращает ЧЕРНОВИК со сметой и числом кандидатов; launch переводит его
|
|
* в очередь и запускает робота кабинета МТС (в песочнице — только черновик).
|
|
*/
|
|
|
|
/** Способ выбора аудитории: свежие сделки за период / своя база / свой список. */
|
|
export type TelegramAudienceKind = 'deals' | 'base' | 'list';
|
|
|
|
/** Кампания клиентской Telegram-рекламы (строка списка, POST-ответ и часть GET .../{id}). */
|
|
export interface TelegramCampaign {
|
|
id: number;
|
|
status: string;
|
|
status_reason?: string | null;
|
|
ad_text: string;
|
|
ad_link: string;
|
|
media_path: string | null;
|
|
ord_category: string;
|
|
budget_cap_rub: string;
|
|
audience_kind: TelegramAudienceKind;
|
|
audience_params: Record<string, unknown> | null;
|
|
planned_count: number;
|
|
matched_count: number | null;
|
|
estimated_cost_rub: string;
|
|
actual_cost_rub: string | null;
|
|
created_at?: string;
|
|
}
|
|
|
|
/** Ответ GET /api/telegram/campaigns — список кампаний тенанта + режим + баланс. */
|
|
export interface TelegramIndex {
|
|
campaigns: TelegramCampaign[];
|
|
sandbox: boolean;
|
|
balance_rub: string;
|
|
frozen_rub: string;
|
|
}
|
|
|
|
/** Тело POST /api/telegram/campaigns — создание черновика кампании. */
|
|
export interface TelegramCreatePayload {
|
|
ad_text: string;
|
|
ad_link: string;
|
|
audience_kind: TelegramAudienceKind;
|
|
budget_cap_rub: string;
|
|
audience_days?: number;
|
|
ord_category?: string;
|
|
phones?: string[];
|
|
}
|
|
|
|
/** GET /api/telegram/campaigns — список кампаний тенанта + режим + баланс. */
|
|
export async function fetchTelegram(): Promise<TelegramIndex> {
|
|
const { data } = await apiClient.get<TelegramIndex>('/api/telegram/campaigns');
|
|
return data;
|
|
}
|
|
|
|
/** POST /api/telegram/campaigns — создать ЧЕРНОВИК кампании (деньги/робот не трогаются). */
|
|
export async function createTelegram(payload: TelegramCreatePayload): Promise<TelegramCampaign> {
|
|
await ensureCsrfCookie();
|
|
const { data } = await apiClient.post<TelegramCampaign>('/api/telegram/campaigns', payload);
|
|
return data;
|
|
}
|
|
|
|
/** GET /api/telegram/campaigns/{id} — детали кампании. */
|
|
export async function fetchTelegramCampaign(id: number): Promise<{ campaign: TelegramCampaign }> {
|
|
const { data } = await apiClient.get<{ campaign: TelegramCampaign }>(`/api/telegram/campaigns/${id}`);
|
|
return data;
|
|
}
|
|
|
|
/** POST /api/telegram/campaigns/{id}/launch — запустить черновик (draft → queued + робот). */
|
|
export async function launchTelegram(id: number): Promise<TelegramCampaign> {
|
|
await ensureCsrfCookie();
|
|
const { data } = await apiClient.post<TelegramCampaign>(`/api/telegram/campaigns/${id}/launch`);
|
|
return data;
|
|
}
|