Files
app_umbrix/app/api/user-subscription/route.ts

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