// ============================================================================ // Mock API Handlers // ============================================================================ import { mockCurrentUser, mockTokens, mockTargetChannels, mockExternalChannels, mockCreatives, mockPlacements, mockSubscriptions, mockViewsHistory, mockAnalyticsOverview, mockCostsReportByDay, mockCostsReportByWeek, mockCostsReportByMonth, mockAnalyticsChannelsData, mockAnalyticsCreativesData, } from "./index"; import type { ListResponse, SuccessResponse, ApiError, TargetChannel, ExternalChannel, Creative, Placement, PlacementCreateRequest, PlacementUpdateRequest, CreativeCreateRequest, ExternalChannelCreateRequest, ViewsFetchResponse, ViewsHistoryResponse, AuthCompleteRequest, AuthCompleteResponse, AuthRefreshRequest, AuthRefreshResponse, AuthInitResponse, ExternalChannelUpdateRequest, CreativeUpdateRequest, ExternalChannelsListResponse, } 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 | { token: string | null } ): Promise<{ access_token: string }> { await delay(); if (!data.token) { throw mockError("VALIDATION_ERROR", "Token is required"); } // Save mock user to localStorage for later retrieval if (typeof window !== "undefined") { localStorage.setItem("tgex_user", JSON.stringify(mockCurrentUser)); } return { access_token: mockTokens.access_token, }; }, 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() { await delay(); return { target_channels: mockTargetChannels, }; }, 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( targetChannelId: string ): Promise { await delay(); // Возвращаем все каналы для указанного target channel // В реальном API это будет фильтрация по связям через промежуточную таблицу return { external_channels: mockExternalChannels, }; }, async getExternalChannel(id: string): Promise { await delay(); const channel = mockExternalChannels.find((c) => c.id === id); if (!channel) { throw mockError("NOT_FOUND", "External channel not found"); } return channel; }, async createExternalChannel( data: ExternalChannelCreateRequest ): Promise { await delay(500); const newChannel: ExternalChannel = { id: generateId(), telegram_id: data.telegram_id, title: data.title, username: data.username || null, subscribers_count: data.subscribers_count || null, description: data.description || 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, }; }, 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 updateExternalChannelLinks( id: string, data: { add_target_channel_ids?: string[]; remove_target_channel_ids?: string[]; } ): Promise { await delay(); const channel = mockExternalChannels.find((c) => c.id === id); if (!channel) { throw mockError("NOT_FOUND", "External channel not found"); } // В упрощенной версии просто возвращаем канал return channel; }, // -------------------------------------------------------------------------- // Creatives // -------------------------------------------------------------------------- async getCreatives(params?: { target_channel_id?: string; include_archived?: boolean; }) { await delay(); let filtered = [...mockCreatives]; if (params?.target_channel_id) { filtered = filtered.filter( (c) => c.target_channel_id === params.target_channel_id ); } if (!params?.include_archived) { filtered = filtered.filter((c) => c.status !== "archived"); } return { creatives: filtered, }; }, async getCreative(id: string): Promise { await delay(); const creative = mockCreatives.find((c) => c.id === id); if (!creative) { throw mockError("NOT_FOUND", "Creative not found"); } return creative; }, async createCreative(data: CreativeCreateRequest): Promise { await delay(500); 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, target_channel_title: targetChannel.title, created_at: new Date().toISOString(), status: "active", placements_count: 0, }; 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, }; }, 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.placements_count > 0) { throw mockError( "CREATIVE_IN_USE", "Cannot delete creative with active placements" ); } const index = mockCreatives.findIndex((c) => c.id === id); mockCreatives.splice(index, 1); return { success: true }; }, // -------------------------------------------------------------------------- // Placements (Purchases) // -------------------------------------------------------------------------- async getPlacements(params?: { target_channel_id?: string; external_channel_id?: string; creative_id?: string; include_archived?: boolean; }) { await delay(); let filtered = [...mockPlacements]; 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?.include_archived) { filtered = filtered.filter((p) => p.status !== "archived"); } return { placements: filtered, }; }, async getPlacement(id: string): Promise { await delay(); const placement = mockPlacements.find((p) => p.id === id); if (!placement) { throw mockError("NOT_FOUND", "Placement not found"); } return placement; }, async createPlacement(data: PlacementCreateRequest): 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 newPlacement: Placement = { id: generateId(), target_channel_id: data.target_channel_id, target_channel_title: targetChannel.title, external_channel_id: data.external_channel_id, external_channel_title: externalChannel.title, creative_id: data.creative_id, creative_name: creative.name, placement_date: data.placement_date, cost: data.cost || null, comment: data.comment || null, ad_post_url: data.ad_post_url || null, invite_link_type: data.invite_link_type || "public", invite_link: inviteLink, status: "active", subscriptions_count: 0, views_count: null, views_availability: "unknown", last_views_fetch_at: null, created_at: new Date().toISOString(), }; mockPlacements.push(newPlacement); return newPlacement; }, async updatePlacement( id: string, data: PlacementUpdateRequest ): Promise { await delay(); const placement = mockPlacements.find((p) => p.id === id); if (!placement) { throw mockError("NOT_FOUND", "Placement not found"); } return { ...placement, ...data, }; }, async deletePlacement(id: string): Promise { await delay(); const index = mockPlacements.findIndex((p) => p.id === id); if (index === -1) { throw mockError("NOT_FOUND", "Placement not found"); } mockPlacements.splice(index, 1); return { success: true }; }, async fetchPlacementViews(id: string): Promise { await delay(1000); const placement = mockPlacements.find((p) => p.id === id); if (!placement) { throw mockError("NOT_FOUND", "Placement not found"); } // Mock: add some random views const newViews = (placement.views_count || 0) + Math.floor(Math.random() * 500); return { placement_id: id, views_count: newViews, views_availability: "available", fetched_at: new Date().toISOString(), error_message: null, }; }, async getPlacementViewsHistory( id: string, params?: { from_date?: string; to_date?: string } ): Promise { await delay(); const placement = mockPlacements.find((p) => p.id === id); if (!placement) { throw mockError("NOT_FOUND", "Placement not found"); } const filteredHistory = mockViewsHistory.filter( (v) => v.purchase_id === id ); return { histories: filteredHistory, }; }, async setPlacementViewsManually( id: string, views_count: number ): Promise { await delay(); const placement = mockPlacements.find((p) => p.id === id); if (!placement) { throw mockError("NOT_FOUND", "Placement not found"); } return { ...placement, views_count, views_availability: "manual", last_views_fetch_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, }; }, };