import { NextRequest, NextResponse } from 'next/server'; import { MARZBAN_PANEL_URL, getSubscriptionUrl } from '@/lib/constants'; import { logger } from '@/lib/logger'; import type { MarzbanUser } from '@/types/marzban'; // Next.js 13+ caching: no-store для реального времени данных подписки export const dynamic = 'force-dynamic'; export const revalidate = 0; const MARZBAN_API = MARZBAN_PANEL_URL; const ADMIN_USERNAME = process.env.MARZBAN_ADMIN_USERNAME; const ADMIN_PASSWORD = process.env.MARZBAN_ADMIN_PASSWORD; if (!ADMIN_USERNAME || !ADMIN_PASSWORD) { throw new Error('MARZBAN_ADMIN_USERNAME and MARZBAN_ADMIN_PASSWORD must be set in .env'); } export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const telegramId = searchParams.get('telegramId'); const telegramUsername = searchParams.get('telegramUsername'); if (!telegramId && !telegramUsername) { return NextResponse.json( { success: false, error: 'telegramId or telegramUsername required' }, { status: 400 } ); } // 1. Получаем токен админа const tokenResponse = await fetch(`${MARZBAN_API}/api/admin/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `grant_type=password&username=${ADMIN_USERNAME}&password=${ADMIN_PASSWORD}`, // Кешируем токен на 30 минут (стандартное время жизни JWT) cache: 'force-cache', next: { revalidate: 1800 } }); if (!tokenResponse.ok) { throw new Error('Failed to get admin token'); } const { access_token } = await tokenResponse.json(); // 2. Получаем ВСЕХ пользователей (Marzban API не поддерживает фильтр по note) const usersResponse = await fetch(`${MARZBAN_API}/api/users?offset=0&limit=1000`, { method: 'GET', headers: { 'Authorization': `Bearer ${access_token}`, }, // Данные пользователей НЕ кешируем - нужны в реальном времени cache: 'no-store', next: { revalidate: 0 } }); if (!usersResponse.ok) { throw new Error('Failed to get users'); } const usersData = await usersResponse.json(); logger.debug(`🔍 Searching for telegramId: ${telegramId}, username: ${telegramUsername}`); logger.debug(`📊 Total users in Marzban: ${usersData.users?.length || 0}`); // 3. Ищем пользователя по Telegram ID в note или по username const user: MarzbanUser | undefined = usersData.users?.find((u: any) => { // Поиск по note: "TG: 1270320642" if (telegramId && u.note?.includes(`TG: ${telegramId}`)) { return true; } // Поиск по username patterns if (telegramUsername) { const cleanUsername = telegramUsername.toLowerCase().replace(/[^a-z0-9_]/g, '_'); if (u.username === cleanUsername) { return true; } } // Поиск по username с ID: "user_1270320642" или "имя_1270320642" if (telegramId && u.username?.includes(`_${telegramId}`)) { return true; } return false; }); if (!user) { logger.debug('❌ User not found'); return NextResponse.json( { success: false, hasSubscription: false }, { status: 200 } ); } logger.debug(`✅ Found user: ${user.username}`); // 4. Получаем subscription token из subscription_url const subscriptionToken = user.subscription_url?.split('/sub/')[1]?.replace(/\/$/, '') || user.username; return NextResponse.json({ success: true, hasSubscription: true, token: subscriptionToken, username: user.username, status: user.status, expire: user.expire, dataLimit: user.data_limit, dataLimitResetStrategy: user.data_limit_reset_strategy, usedTraffic: user.used_traffic, subscriptionUrl: getSubscriptionUrl(subscriptionToken), }); } catch (error) { logger.error('Get subscription error:', error); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } }