Реферальная система: БД, API, UI

**База данных:**
- Создана таблица referrals в MariaDB (193.168.175.128)
- Поля: username, referrer_username, referral_count, bonus_days_earned
- Foreign keys к users таблице с CASCADE/SET NULL

**API Endpoints:**
- POST /api/referral/track - отслеживание новых рефералов
  - Автоматический расчёт бонусов: +7 дней за каждого
  - Milestone bonus: +30 дней за каждые 5 рефералов
  - Обновление expire даты реферера в users таблице

- GET /api/referral/stats?username=xxx - статистика
  - Возвращает количество рефералов, бонусные дни
  - Список приглашённых пользователей со статусами

**Интеграция:**
- POST /api/create-user принимает referrerId параметр
- Автоматический вызов /api/referral/track после создания юзера
- Параметр ref из URL при активации trial

**UI:**
- /app/referral/page.tsx - страница статистики
  - 3 KPI карточки: рефералов, бонусных дней, milestone
  - Реферальная ссылка с кнопкой копирования
  - Список приглашённых юзеров с иконками статуса
  - Инфоблок о механике начисления бонусов

**ReferralModal обновлён:**
- Добавлена кнопка «Моя статистика» → /referral
- Перенос Share/Copy кнопок на второй/третий план

**Зависимости:**
- mysql2@3.16.3 - для подключения к MariaDB

**Логика бонусов:**
- +7 дней за каждого успешного реферала
- +30 дней бонус за каждые 5 рефералов (milestone)
- Автоматическое обновление expire поля в users таблице
- Сохранение всех бонусов в bonus_days_earned
This commit is contained in:
Umbrix Dev
2026-02-06 20:51:40 +03:00
parent 00bfda8748
commit b43eb3c724
8 changed files with 684 additions and 5 deletions

View File

@@ -0,0 +1,90 @@
// API endpoint для получения статистики реферальной программы
// GET /api/referral/stats?username=xxx - возвращает статистику пользователя
import { NextRequest, NextResponse } from 'next/server';
import mysql from 'mysql2/promise';
// Database connection config
const dbConfig = {
host: '193.168.175.128',
user: 'marzban_user',
password: '2CuopqFd0Y5V5n/qBM+eygOQb6aC8B8pACcdHjeVJsE=',
database: 'marzban_prod',
};
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const username = searchParams.get('username');
if (!username) {
return NextResponse.json(
{ success: false, error: 'Username is required' },
{ status: 400 }
);
}
// Connect to database
const connection = await mysql.createConnection(dbConfig);
try {
// Get user's referral stats
const [statsRows] = await connection.query(
'SELECT * FROM referrals WHERE username = ?',
[username]
);
if ((statsRows as any[]).length === 0) {
return NextResponse.json({
success: true,
hasReferrals: false,
stats: {
referral_count: 0,
bonus_days_earned: 0,
total_referrals_used: 0,
},
});
}
const stats = (statsRows as any[])[0];
// Get list of referred users
const [referredRows] = await connection.query(
`SELECT u.username, u.created_at, u.status
FROM referrals r
JOIN users u ON r.username = u.username
WHERE r.referrer_username = ?
ORDER BY r.created_at DESC`,
[username]
);
return NextResponse.json({
success: true,
hasReferrals: true,
stats: {
referral_count: stats.referral_count || 0,
bonus_days_earned: stats.bonus_days_earned || 0,
total_referrals_used: stats.total_referrals_used || 0,
created_at: stats.created_at,
updated_at: stats.updated_at,
},
referred_users: (referredRows as any[]).map((user: any) => ({
username: user.username,
created_at: user.created_at,
status: user.status,
})),
});
} finally {
await connection.end();
}
} catch (error) {
console.error('Referral stats error:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,135 @@
// API endpoint для отслеживания реферальной регистрации
// POST /api/referral/track - записывает нового пользователя с реферером
import { NextRequest, NextResponse } from 'next/server';
import mysql from 'mysql2/promise';
// Database connection config
const dbConfig = {
host: '193.168.175.128',
user: 'marzban_user',
password: '2CuopqFd0Y5V5n/qBM+eygOQb6aC8B8pACcdHjeVJsE=',
database: 'marzban_prod',
};
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { username, referrer_username } = body;
if (!username) {
return NextResponse.json(
{ success: false, error: 'Username is required' },
{ status: 400 }
);
}
// Connect to database
const connection = await mysql.createConnection(dbConfig);
try {
// Check if user already exists in referrals table
const [existingRows] = await connection.query(
'SELECT id FROM referrals WHERE username = ?',
[username]
);
if ((existingRows as any[]).length > 0) {
return NextResponse.json({
success: false,
error: 'User already tracked',
});
}
// Insert new referral record
await connection.query(
'INSERT INTO referrals (username, referrer_username) VALUES (?, ?)',
[username, referrer_username || null]
);
// If referrer exists, increment their referral_count
if (referrer_username) {
// Check if referrer exists in users table
const [referrerRows] = await connection.query(
'SELECT username FROM users WHERE username = ?',
[referrer_username]
);
if ((referrerRows as any[]).length > 0) {
// Update or insert referrer's stats
await connection.query(
`INSERT INTO referrals (username, referral_count)
VALUES (?, 1)
ON DUPLICATE KEY UPDATE
referral_count = referral_count + 1,
updated_at = CURRENT_TIMESTAMP`,
[referrer_username]
);
// Calculate bonus days
const [statsRows] = await connection.query(
'SELECT referral_count FROM referrals WHERE username = ?',
[referrer_username]
);
const referralCount = (statsRows as any[])[0]?.referral_count || 0;
let bonusDays = 0;
// +7 days for each referral
bonusDays = referralCount * 7;
// +30 days milestone bonus for every 5 referrals
if (referralCount >= 5) {
const milestones = Math.floor(referralCount / 5);
bonusDays += milestones * 30;
}
// Update bonus_days_earned
await connection.query(
'UPDATE referrals SET bonus_days_earned = ? WHERE username = ?',
[bonusDays, referrer_username]
);
// Add bonus days to referrer's expire date in users table
await connection.query(
`UPDATE users
SET expire = CASE
WHEN expire IS NULL OR expire < UNIX_TIMESTAMP()
THEN UNIX_TIMESTAMP() + (? * 86400)
ELSE expire + (7 * 86400)
END
WHERE username = ?`,
[bonusDays, referrer_username]
);
return NextResponse.json({
success: true,
message: 'Referral tracked successfully',
referrer_bonus: {
username: referrer_username,
new_referral_count: referralCount,
bonus_days_added: 7,
total_bonus_days: bonusDays,
},
});
}
}
return NextResponse.json({
success: true,
message: 'User tracked without referrer',
});
} finally {
await connection.end();
}
} catch (error) {
console.error('Referral track error:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
);
}
}