import { NextRequest, NextResponse } from 'next/server'; import connectMongoDB from '@/lib/mongodb'; import FunnelModel from '@/lib/models/Funnel'; import FunnelHistoryModel from '@/lib/models/FunnelHistory'; import type { FunnelDefinition } from '@/lib/funnel/types'; // GET /api/funnels - получить список всех воронок export async function GET(request: NextRequest) { try { await connectMongoDB(); const { searchParams } = new URL(request.url); const status = searchParams.get('status'); const search = searchParams.get('search'); const limit = parseInt(searchParams.get('limit') || '20'); const page = parseInt(searchParams.get('page') || '1'); const sortBy = searchParams.get('sortBy') || 'updatedAt'; const sortOrder = searchParams.get('sortOrder') || 'desc'; // Строим фильтр const filter: Record = {}; if (status && ['draft', 'published', 'archived'].includes(status)) { filter.status = status; } if (search) { filter.$or = [ { name: { $regex: search, $options: 'i' } }, { description: { $regex: search, $options: 'i' } }, { 'funnelData.meta.title': { $regex: search, $options: 'i' } }, { 'funnelData.meta.description': { $regex: search, $options: 'i' } } ]; } // Строим сортировку const sort: Record = {}; sort[sortBy] = sortOrder === 'desc' ? -1 : 1; // Выполняем запрос с пагинацией const skip = (page - 1) * limit; const [funnels, total] = await Promise.all([ FunnelModel .find(filter) .select('-funnelData.screens -funnelData.defaultTexts') // Исключаем тяжелые данные, но оставляем meta .sort(sort) .skip(skip) .limit(limit) .lean(), FunnelModel.countDocuments(filter) ]); return NextResponse.json({ funnels, pagination: { current: page, total: Math.ceil(total / limit), count: funnels.length, totalItems: total } }); } catch (error) { console.error('GET /api/funnels error:', error); return NextResponse.json( { error: 'Failed to fetch funnels' }, { status: 500 } ); } } // POST /api/funnels - создать новую воронку export async function POST(request: NextRequest) { try { await connectMongoDB(); const body = await request.json(); const { name, description, funnelData, status = 'draft' } = body; // Валидация if (!name || !funnelData) { return NextResponse.json( { error: 'Name and funnel data are required' }, { status: 400 } ); } if (!funnelData.meta || !funnelData.meta.id || !Array.isArray(funnelData.screens)) { return NextResponse.json( { error: 'Invalid funnel data structure' }, { status: 400 } ); } // Проверяем уникальность funnelData.meta.id const existingFunnel = await FunnelModel.findOne({ 'funnelData.meta.id': funnelData.meta.id }); if (existingFunnel) { return NextResponse.json( { error: 'Funnel with this ID already exists' }, { status: 409 } ); } // Создаем воронку const funnel = new FunnelModel({ name, description, funnelData: funnelData as FunnelDefinition, status, version: 1, usage: { totalViews: 0, totalCompletions: 0 } }); const savedFunnel = await funnel.save(); // Создаем базовую точку в истории const sessionId = `create-${Date.now()}`; await FunnelHistoryModel.create({ funnelId: String(savedFunnel._id), sessionId, funnelSnapshot: funnelData, actionType: 'create', sequenceNumber: 0, description: 'Воронка создана', isBaseline: true }); return NextResponse.json({ _id: savedFunnel._id, name: savedFunnel.name, description: savedFunnel.description, status: savedFunnel.status, version: savedFunnel.version, createdAt: savedFunnel.createdAt, updatedAt: savedFunnel.updatedAt, usage: savedFunnel.usage, funnelData: savedFunnel.funnelData }, { status: 201 }); } catch (error) { console.error('POST /api/funnels error:', error); if (error instanceof Error && error.message.includes('duplicate key')) { return NextResponse.json( { error: 'Funnel with this name already exists' }, { status: 409 } ); } return NextResponse.json( { error: 'Failed to create funnel' }, { status: 500 } ); } }