// API endpoint для создания нового пользователя VPN // POST /api/create-user // Принимает: { planType, telegramId, telegramUsername, firstName, lastName } // Возвращает: { success: true, token, username, subscriptionUrl } import { NextRequest, NextResponse } from 'next/server'; import { randomUUID } from 'crypto'; // Криптографически безопасная генерация UUID import { MARZBAN_PANEL_URL, getSubscriptionUrl } from '@/lib/constants'; import { logger } from '@/lib/logger'; import type { PlanType, CreateUserRequest, CreateUserResponse } from '@/types/marzban'; 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 POST(request: NextRequest) { try { const { planType, telegramId, telegramUsername, firstName, lastName } = await request.json(); logger.debug('📥 Received data:', { planType, telegramId, telegramUsername, firstName, lastName }); // 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}`, }); if (!tokenResponse.ok) { throw new Error('Failed to get admin token'); } const { access_token } = await tokenResponse.json(); // 2. Определяем параметры тарифа const planConfig = getPlanConfig(planType); // 3. Генерируем уникальное имя пользователя (приоритет: @username > firstName_ID > userID > random) // ВАЖНО: Marzban принимает только a-z, 0-9 и подчеркивания, БЕЗ @ let username: string; if (telegramUsername) { // Есть @username в Telegram - используем БЕЗ @ username = telegramUsername.toLowerCase().replace(/[^a-z0-9_]/g, '_'); } else if (firstName && telegramId) { // Нет username, используем имя + ID const cleanName = firstName.toLowerCase().replace(/[^a-z0-9]/g, '_'); username = `${cleanName}_${telegramId}`; } else if (telegramId) { // Только ID username = `user_${telegramId}`; } else { // Для тестирования вне Telegram username = `user_${Date.now()}_${Math.random().toString(36).substring(7)}`; } console.log('✅ Generated username:', username); logger.debug('✅ Generated username:', username); logger.debug('📊 Telegram data:', { telegramId, telegramUsername, firstName, lastName }); // 4. Создаем пользователя в Marzban по ПРАВИЛЬНОЙ схеме const createUserResponse = await fetch(`${MARZBAN_API}/api/user`, { method: 'POST', headers: { 'Authorization': `Bearer ${access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ username: username, status: 'active', expire: planConfig.expireTimestamp, data_limit: planConfig.dataLimitBytes, data_limit_reset_strategy: 'no_reset', proxies: { vmess: { id: randomUUID() // Криптографически безопасный UUID }, vless: {}, trojan: {} }, inbounds: { vless: ['VLESS TCP', 'VLESS Reality'], vmess: ['VMess WS'], trojan: ['Trojan TCP'] }, note: `Тариф: ${planConfig.name}, TG: ${telegramId || 'N/A'}, Создан: ${new Date().toISOString()}`, }), }); if (!createUserResponse.ok) { const errorText = await createUserResponse.text(); throw new Error(`Failed to create user: ${errorText}`); } const userData = await createUserResponse.json(); // 5. Получаем subscription token const subscriptionToken = userData.subscription_url?.split('/sub/')[1]?.replace(/\/$/, '') || username; return NextResponse.json({ success: true, token: subscriptionToken, username: username, expiresAt: new Date(planConfig.expireTimestamp * 1000).toISOString(), subscriptionUrl: getSubscriptionUrl(subscriptionToken), }); } catch (error) { logger.error('Create user error:', error); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } } function getPlanConfig(planType: PlanType) { const now = Math.floor(Date.now() / 1000); const configs: Record = { trial: { name: '7 дней бесплатно', expireTimestamp: now + (7 * 24 * 60 * 60), // 7 дней dataLimitBytes: 0, // безлимит }, start: { name: 'Базовый', expireTimestamp: now + (30 * 24 * 60 * 60), // 30 дней dataLimitBytes: 50 * 1024 * 1024 * 1024, // 50 ГБ }, plus: { name: 'Расширенный', expireTimestamp: now + (30 * 24 * 60 * 60), // 30 дней dataLimitBytes: 299 * 1024 * 1024 * 1024, // 299 ГБ }, max: { name: 'Премиум', expireTimestamp: now + (30 * 24 * 60 * 60), // 30 дней dataLimitBytes: 0, // безлимит }, }; return configs[planType] || configs.trial; }