// ============================================================================ // Mock API Handlers // ============================================================================ import { mockCurrentUser, mockTokens, mockTargetChannels, getMockTargetChannelDetail, mockExternalChannels, getMockExternalChannelDetail, mockCreatives, getMockCreativeDetail, mockPurchases, getMockPurchaseDetail, mockAnalyticsOverview, mockCostsReportByDay, mockCostsReportByWeek, mockCostsReportByMonth, mockAnalyticsChannelsData, mockAnalyticsCreativesData, } from "./index"; import type { ListResponse, SuccessResponse, ApiError, TargetChannel, ExternalChannel, Creative, Purchase, PurchaseCreateRequest, PurchaseCreateResponse, CreativeCreateRequest, ExternalChannelCreateRequest, PurchaseRefreshViewsResponse, ExternalChannelImportResponse, AuthCompleteRequest, AuthCompleteResponse, AuthRefreshRequest, AuthRefreshResponse, AuthInitResponse, TargetChannelUpdateRequest, ExternalChannelUpdateRequest, CreativeUpdateRequest, PurchaseUpdateRequest, } from "@/lib/types/api"; // Simulate network delay const delay = (ms: number = 300) => new Promise((resolve) => setTimeout(resolve, ms)); // Helper to generate mock error const mockError = (code: string, message: string): ApiError => ({ error: { code, message }, }); // Helper to generate IDs let idCounter = 1000; const generateId = () => `mock_${idCounter++}`; // ============================================================================ // Mock API Handlers // ============================================================================ export const mockApiHandlers = { // -------------------------------------------------------------------------- // Auth // -------------------------------------------------------------------------- async authInit(): Promise { await delay(); return { bot_username: "tgex_bot", start_param: "login", }; }, async authComplete(data: AuthCompleteRequest): Promise { await delay(); if (!data.token) { throw mockError("VALIDATION_ERROR", "Token is required"); } return { access_token: mockTokens.access_token, refresh_token: mockTokens.refresh_token, user: mockCurrentUser, }; }, async authRefresh(data: AuthRefreshRequest): Promise { await delay(); if (!data.refresh_token) { throw mockError("VALIDATION_ERROR", "Refresh token is required"); } return { access_token: `${mockTokens.access_token}_refreshed`, }; }, async authMe() { await delay(); return mockCurrentUser; }, // -------------------------------------------------------------------------- // Target Channels // -------------------------------------------------------------------------- async getTargetChannels(params?: { is_active?: boolean; }): Promise> { await delay(); let filtered = [...mockTargetChannels]; if (params?.is_active !== undefined) { filtered = filtered.filter((c) => c.is_active === params.is_active); } return { data: filtered, total: filtered.length, }; }, async getTargetChannel(id: string) { await delay(); const detail = getMockTargetChannelDetail(id); if (!detail) { throw mockError("NOT_FOUND", "Target channel not found"); } return detail; }, async updateTargetChannel( id: string, data: TargetChannelUpdateRequest ): Promise { await delay(); const channel = mockTargetChannels.find((c) => c.id === id); if (!channel) { throw mockError("NOT_FOUND", "Target channel not found"); } return { ...channel, is_active: data.is_active, updated_at: new Date().toISOString(), }; }, async deleteTargetChannel(id: string): Promise { await delay(); const channel = mockTargetChannels.find((c) => c.id === id); if (!channel) { throw mockError("NOT_FOUND", "Target channel not found"); } return { success: true }; }, // -------------------------------------------------------------------------- // External Channels // -------------------------------------------------------------------------- async getExternalChannels(params?: { target_channel_id?: string; search?: string; }): Promise> { await delay(); let filtered = [...mockExternalChannels]; if (params?.target_channel_id) { filtered = filtered.filter((c) => c.target_channels.includes(params.target_channel_id!) ); } if (params?.search) { const search = params.search.toLowerCase(); filtered = filtered.filter( (c) => c.title.toLowerCase().includes(search) || c.username?.toLowerCase().includes(search) ); } return { data: filtered, total: filtered.length, }; }, async getExternalChannel(id: string) { await delay(); const detail = getMockExternalChannelDetail(id); if (!detail) { throw mockError("NOT_FOUND", "External channel not found"); } return detail; }, async createExternalChannel( data: ExternalChannelCreateRequest ): Promise { await delay(500); const newChannel: ExternalChannel = { id: generateId(), telegram_id: null, title: data.title, username: data.username || null, link: data.link, subscribers_count: data.subscribers_count || null, description: data.description || null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), target_channels: data.target_channel_ids, total_purchases: 0, avg_cpf: null, avg_cpm: null, }; mockExternalChannels.push(newChannel); return newChannel; }, async updateExternalChannel( id: string, data: ExternalChannelUpdateRequest ): Promise { await delay(); const channel = mockExternalChannels.find((c) => c.id === id); if (!channel) { throw mockError("NOT_FOUND", "External channel not found"); } return { ...channel, ...data, updated_at: new Date().toISOString(), }; }, async deleteExternalChannel(id: string): Promise { await delay(); const index = mockExternalChannels.findIndex((c) => c.id === id); if (index === -1) { throw mockError("NOT_FOUND", "External channel not found"); } mockExternalChannels.splice(index, 1); return { success: true }; }, async importExternalChannels( file: File, targetChannelId: string ): Promise { await delay(1500); // Longer delay for file processing // Mock: simulate importing 3-5 channels const importCount = Math.floor(Math.random() * 3) + 3; const imported: ExternalChannel[] = []; for (let i = 0; i < importCount; i++) { const newChannel: ExternalChannel = { id: generateId(), telegram_id: null, title: `Импортированный канал ${i + 1}`, username: `imported_channel_${i + 1}`, link: `https://t.me/imported_channel_${i + 1}`, subscribers_count: Math.floor(Math.random() * 100000) + 10000, description: "Импортировано из Excel", created_at: new Date().toISOString(), updated_at: new Date().toISOString(), target_channels: [targetChannelId], total_purchases: 0, avg_cpf: null, avg_cpm: null, }; imported.push(newChannel); mockExternalChannels.push(newChannel); } return { imported: importCount, skipped: 1, errors: ["Строка 15: некорректный формат ссылки"], channels: imported, }; }, // -------------------------------------------------------------------------- // Creatives // -------------------------------------------------------------------------- async getCreatives(params?: { target_channel_id?: string; is_archived?: boolean; }): Promise> { await delay(); let filtered = [...mockCreatives]; if (params?.target_channel_id) { filtered = filtered.filter( (c) => c.target_channel_id === params.target_channel_id ); } if (params?.is_archived !== undefined) { filtered = filtered.filter((c) => c.is_archived === params.is_archived); } return { data: filtered, total: filtered.length, }; }, async getCreative(id: string) { await delay(); const detail = getMockCreativeDetail(id); if (!detail) { throw mockError("NOT_FOUND", "Creative not found"); } return detail; }, async createCreative(data: CreativeCreateRequest): Promise { await delay(500); // Validate {link} placeholder if (!data.text.includes("{link}")) { throw mockError( "VALIDATION_ERROR", "Creative text must contain {link} placeholder" ); } const targetChannel = mockTargetChannels.find( (c) => c.id === data.target_channel_id ); if (!targetChannel) { throw mockError("NOT_FOUND", "Target channel not found"); } const newCreative: Creative = { id: generateId(), name: data.name, text: data.text, target_channel_id: data.target_channel_id, is_archived: false, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), target_channel: targetChannel, total_purchases: 0, total_subscriptions: 0, avg_cpf: null, }; mockCreatives.push(newCreative); return newCreative; }, async updateCreative( id: string, data: CreativeUpdateRequest ): Promise { await delay(); const creative = mockCreatives.find((c) => c.id === id); if (!creative) { throw mockError("NOT_FOUND", "Creative not found"); } if (data.text && !data.text.includes("{link}")) { throw mockError( "VALIDATION_ERROR", "Creative text must contain {link} placeholder" ); } return { ...creative, ...data, updated_at: new Date().toISOString(), }; }, async deleteCreative(id: string): Promise { await delay(); const creative = mockCreatives.find((c) => c.id === id); if (!creative) { throw mockError("NOT_FOUND", "Creative not found"); } if (creative.total_purchases > 0) { throw mockError( "CREATIVE_IN_USE", "Cannot delete creative with active purchases" ); } const index = mockCreatives.findIndex((c) => c.id === id); mockCreatives.splice(index, 1); return { success: true }; }, // -------------------------------------------------------------------------- // Purchases // -------------------------------------------------------------------------- async getPurchases(params?: { target_channel_id?: string; external_channel_id?: string; creative_id?: string; is_archived?: boolean; date_from?: string; date_to?: string; sort?: string; order?: "asc" | "desc"; }): Promise> { await delay(); let filtered = [...mockPurchases]; if (params?.target_channel_id) { filtered = filtered.filter( (p) => p.target_channel_id === params.target_channel_id ); } if (params?.external_channel_id) { filtered = filtered.filter( (p) => p.external_channel_id === params.external_channel_id ); } if (params?.creative_id) { filtered = filtered.filter((p) => p.creative_id === params.creative_id); } if (params?.is_archived !== undefined) { filtered = filtered.filter((p) => p.is_archived === params.is_archived); } // Sorting if (params?.sort) { filtered.sort((a, b) => { let aVal: any; let bVal: any; switch (params.sort) { case "date": aVal = a.actual_date || a.scheduled_date || a.created_at; bVal = b.actual_date || b.scheduled_date || b.created_at; break; case "cost": aVal = a.cost || 0; bVal = b.cost || 0; break; case "cpf": aVal = a.cpf || 0; bVal = b.cpf || 0; break; case "subscriptions": aVal = a.subscriptions_count; bVal = b.subscriptions_count; break; default: return 0; } if (params.order === "desc") { return bVal > aVal ? 1 : -1; } return aVal > bVal ? 1 : -1; }); } return { data: filtered, total: filtered.length, }; }, async getPurchase(id: string) { await delay(); const detail = getMockPurchaseDetail(id); if (!detail) { throw mockError("NOT_FOUND", "Purchase not found"); } return detail; }, async createPurchase( data: PurchaseCreateRequest ): Promise { await delay(800); const targetChannel = mockTargetChannels.find( (c) => c.id === data.target_channel_id ); const externalChannel = mockExternalChannels.find( (c) => c.id === data.external_channel_id ); const creative = mockCreatives.find((c) => c.id === data.creative_id); if (!targetChannel || !externalChannel || !creative) { throw mockError("NOT_FOUND", "One of the required entities not found"); } // Generate mock invite link const inviteLink = `https://t.me/+${Math.random() .toString(36) .substring(2, 15)}`; const newPurchase: Purchase = { id: generateId(), target_channel_id: data.target_channel_id, external_channel_id: data.external_channel_id, creative_id: data.creative_id, scheduled_date: data.scheduled_date || null, actual_date: data.actual_date || null, cost: data.cost || null, comment: data.comment || null, post_link: data.post_link || null, invite_link: inviteLink, invite_link_type: data.invite_link_type, is_archived: false, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), target_channel: targetChannel, external_channel: externalChannel, creative: creative, subscriptions_count: 0, views_count: null, cpf: null, cpm: null, conversion_rate: null, }; mockPurchases.push(newPurchase); // Format message with invite link const formattedMessage = creative.text.replace("{link}", inviteLink); return { ...newPurchase, formatted_message: formattedMessage, }; }, async updatePurchase( id: string, data: PurchaseUpdateRequest ): Promise { await delay(); const purchase = mockPurchases.find((p) => p.id === id); if (!purchase) { throw mockError("NOT_FOUND", "Purchase not found"); } return { ...purchase, ...data, updated_at: new Date().toISOString(), }; }, async deletePurchase(id: string): Promise { await delay(); const index = mockPurchases.findIndex((p) => p.id === id); if (index === -1) { throw mockError("NOT_FOUND", "Purchase not found"); } mockPurchases.splice(index, 1); return { success: true }; }, async refreshPurchaseViews( id: string ): Promise { await delay(1000); const purchase = mockPurchases.find((p) => p.id === id); if (!purchase) { throw mockError("NOT_FOUND", "Purchase not found"); } // Mock: add some random views const newViews = (purchase.views_count || 0) + Math.floor(Math.random() * 500); return { views: newViews, fetched_at: new Date().toISOString(), }; }, // -------------------------------------------------------------------------- // Analytics // -------------------------------------------------------------------------- async getAnalyticsOverview(params?: { target_channel_id?: string; date_from?: string; date_to?: string; }) { await delay(); return mockAnalyticsOverview; }, async getAnalyticsCosts(params?: { period?: "day" | "week" | "month"; target_channel_id?: string; }) { await delay(); const period = params?.period || "week"; if (period === "day") { return mockCostsReportByDay; } else if (period === "month") { return mockCostsReportByMonth; } return mockCostsReportByWeek; }, async getAnalyticsChannels(params?: { target_channel_id?: string; date_from?: string; date_to?: string; type?: "external" | "target"; }) { await delay(); return { data: mockAnalyticsChannelsData, }; }, async getAnalyticsCreatives(params?: { target_channel_id?: string; date_from?: string; date_to?: string; }) { await delay(); return { data: mockAnalyticsCreativesData, }; }, };