📡 API: Получение информации о подписке пользователя

This commit is contained in:
Umbrix Dev
2026-02-04 05:17:13 +03:00
parent 3a6f034bc8
commit 51c6ec1ffa

View File

@@ -0,0 +1,128 @@
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 }
);
}
}