129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
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 }
|
||
);
|
||
}
|
||
}
|