w-funnel/src/app/api/funnels/route.ts
dev.daminik00 0fc1dc756e admin
2025-09-27 05:48:42 +02:00

163 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, unknown> = {};
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<string, 1 | -1> = {};
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 }
);
}
}