w-funnel/src/app/api/funnels/[id]/history/route.ts
2025-09-28 17:30:32 +02:00

152 lines
4.3 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { adminApiDisabledResponse, isAdminApiEnabled } from '@/lib/runtime/adminApi';
interface RouteParams {
params: Promise<{
id: string;
}>;
}
// GET /api/funnels/[id]/history - получить историю изменений воронки
export async function GET(request: NextRequest, { params }: RouteParams) {
if (!isAdminApiEnabled()) {
return adminApiDisabledResponse();
}
try {
const { id } = await params;
const [
{ default: connectMongoDB },
{ default: FunnelHistoryModel },
] = await Promise.all([
import('@/lib/mongodb'),
import('@/lib/models/FunnelHistory'),
]);
await connectMongoDB();
const { searchParams } = new URL(request.url);
const sessionId = searchParams.get('sessionId');
const limit = parseInt(searchParams.get('limit') || '50');
const includeSnapshots = searchParams.get('snapshots') === 'true';
const filter: Record<string, unknown> = { funnelId: id };
if (sessionId) {
filter.sessionId = sessionId;
}
const historyQuery = FunnelHistoryModel
.find(filter)
.sort({ createdAt: -1, sequenceNumber: -1 })
.limit(limit);
// Включать снимки данных или только метаданные
if (!includeSnapshots) {
historyQuery.select('-funnelSnapshot');
}
const history = await historyQuery.lean();
return NextResponse.json({
history: history.map(entry => ({
...entry,
_id: entry._id.toString(),
funnelId: entry.funnelId.toString()
}))
});
} catch (error) {
console.error('GET /api/funnels/[id]/history error:', error);
return NextResponse.json(
{ error: 'Failed to fetch funnel history' },
{ status: 500 }
);
}
}
// POST /api/funnels/[id]/history - создать новую запись в истории
export async function POST(request: NextRequest, { params }: RouteParams) {
if (!isAdminApiEnabled()) {
return adminApiDisabledResponse();
}
try {
const { id } = await params;
const [
{ default: connectMongoDB },
{ default: FunnelHistoryModel },
] = await Promise.all([
import('@/lib/mongodb'),
import('@/lib/models/FunnelHistory'),
]);
await connectMongoDB();
const body = await request.json();
const {
sessionId,
funnelSnapshot,
actionType,
description,
changeDetails
} = body;
// Валидация
if (!sessionId || !funnelSnapshot || !actionType) {
return NextResponse.json(
{ error: 'sessionId, funnelSnapshot and actionType are required' },
{ status: 400 }
);
}
// Получаем следующий номер последовательности
const lastEntry = await FunnelHistoryModel
.findOne({ funnelId: id, sessionId })
.sort({ sequenceNumber: -1 });
const nextSequenceNumber = (lastEntry?.sequenceNumber || -1) + 1;
// Создаем запись
const historyEntry = await FunnelHistoryModel.create({
funnelId: id,
sessionId,
funnelSnapshot,
actionType,
sequenceNumber: nextSequenceNumber,
description,
changeDetails
});
// Очищаем старые записи истории (оставляем последние 100)
const KEEP_ENTRIES = 100;
const entriesToDelete = await FunnelHistoryModel
.find({ funnelId: id, sessionId })
.sort({ sequenceNumber: -1 })
.skip(KEEP_ENTRIES)
.select('_id');
if (entriesToDelete.length > 0) {
const idsToDelete = entriesToDelete.map((entry: { _id: unknown }) => entry._id);
await FunnelHistoryModel.deleteMany({ _id: { $in: idsToDelete } });
}
return NextResponse.json({
_id: historyEntry._id,
actionType: historyEntry.actionType,
description: historyEntry.description,
sequenceNumber: historyEntry.sequenceNumber,
isBaseline: historyEntry.isBaseline,
createdAt: historyEntry.createdAt,
changeDetails: historyEntry.changeDetails
}, { status: 201 });
} catch (error) {
console.error('POST /api/funnels/[id]/history error:', error);
return NextResponse.json(
{ error: 'Failed to create history entry' },
{ status: 500 }
);
}
}