trial updates

This commit is contained in:
2026-04-24 15:51:03 +07:00
parent 808c07855c
commit 24e3a6de44
4 changed files with 126 additions and 82 deletions
+35 -14
View File
@@ -9,8 +9,15 @@ pub struct User {
pub tg_id: i64,
pub tg_username: Option<String>,
pub balance: i64,
pub game_tickets: i32, // Добавлено
pub has_used_trial: bool, // Добавлено
pub game_tickets: i32,
pub has_used_trial: bool,
}
// 🔥 ДОБАВЛЕНО: Структура подписки
#[derive(Debug, Clone, FromRow, serde::Serialize)]
pub struct Subscription {
pub plan_name: String,
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, FromRow, serde::Serialize)]
@@ -58,6 +65,10 @@ pub trait AppRepository: Send + Sync {
) -> Result<bool, sqlx::Error>;
async fn get_active_nodes(&self) -> Result<Vec<VpnNode>, sqlx::Error>;
async fn add_vpn_node(&self, p: CreateNodePayload) -> Result<VpnNode, sqlx::Error>;
// 🔥 ДОБАВЛЕНО: Новые методы для профиля
async fn get_subscription(&self, user_id: Uuid) -> Result<Option<Subscription>, sqlx::Error>;
async fn get_referral_stats(&self, user_id: Uuid) -> Result<(i64, i64), sqlx::Error>;
}
pub struct PgRepository {
@@ -71,10 +82,7 @@ impl PgRepository {
pub async fn initialize_schema(pool: &PgPool) -> Result<(), Box<dyn std::error::Error>> {
tracing::info!("📦 Применение версионированных миграций (sqlx::migrate)...");
// sqlx сам создаст служебную таблицу _sqlx_migrations и применит нужные файлы
sqlx::migrate!("./migrations").run(pool).await?;
tracing::info!("✅ База данных успешно синхронизирована!");
Ok(())
}
@@ -83,7 +91,7 @@ impl PgRepository {
#[async_trait]
impl AppRepository for PgRepository {
async fn upsert_user(&self, tg_id: i64, username: Option<String>) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>("INSERT INTO users (id, tg_id, tg_username) VALUES ($1, $2, $3) ON CONFLICT (tg_id) DO UPDATE SET tg_username = EXCLUDED.tg_username RETURNING id, tg_id, tg_username, balance")
sqlx::query_as::<_, User>("INSERT INTO users (id, tg_id, tg_username) VALUES ($1, $2, $3) ON CONFLICT (tg_id) DO UPDATE SET tg_username = EXCLUDED.tg_username RETURNING id, tg_id, tg_username, balance, game_tickets, has_used_trial")
.bind(Uuid::new_v4()).bind(tg_id).bind(username).fetch_one(&self.pool).await
}
@@ -124,7 +132,6 @@ impl AppRepository for PgRepository {
.bind(user_id)
.execute(&mut *tx)
.await?;
// Триал на 3 дня
sqlx::query("INSERT INTO subscriptions (user_id, plan_name, expires_at) VALUES ($1, 'TRIAL (5GB)', NOW() + INTERVAL '3 days') ON CONFLICT (user_id) DO UPDATE SET plan_name = EXCLUDED.plan_name, expires_at = EXCLUDED.expires_at")
.bind(user_id).execute(&mut *tx).await?;
tx.commit().await?;
@@ -133,12 +140,10 @@ impl AppRepository for PgRepository {
async fn buy_subscription(&self, user_id: Uuid, plan_name: &str) -> Result<(), sqlx::Error> {
let mut tx = self.pool.begin().await?;
// Даем 1 игровой тикет за покупку
sqlx::query("UPDATE users SET game_tickets = game_tickets + 1 WHERE id = $1")
.bind(user_id)
.execute(&mut *tx)
.await?;
// Даем подписку на 30 дней
sqlx::query("INSERT INTO subscriptions (user_id, plan_name, expires_at) VALUES ($1, $2, NOW() + INTERVAL '30 days') ON CONFLICT (user_id) DO UPDATE SET plan_name = EXCLUDED.plan_name, expires_at = EXCLUDED.expires_at")
.bind(user_id).bind(plan_name).execute(&mut *tx).await?;
tx.commit().await?;
@@ -151,16 +156,14 @@ impl AppRepository for PgRepository {
reward: i64,
) -> Result<bool, sqlx::Error> {
let mut tx = self.pool.begin().await?;
// Проверяем тикеты
let tickets: (i32,) =
sqlx::query_as("SELECT game_tickets FROM users WHERE id = $1 FOR UPDATE")
.bind(user_id)
.fetch_one(&mut *tx)
.await?;
if tickets.0 <= 0 {
return Ok(false); // Нет тикетов
return Ok(false);
}
// Списываем тикет, начисляем баланс
sqlx::query("UPDATE users SET game_tickets = game_tickets - 1, balance = balance + $2 WHERE id = $1")
.bind(user_id).bind(reward).execute(&mut *tx).await?;
tx.commit().await?;
@@ -179,8 +182,8 @@ impl AppRepository for PgRepository {
let id = Uuid::new_v4();
sqlx::query_as::<_, VpnNode>(
r#"
INSERT INTO vpn_nodes (id, name, country_code, ip_address, port, status)
VALUES ($1, $2, $3, $4, $5, 'online')
INSERT INTO vpn_nodes (id, name, country_code, ip_address, port, status, tunnel_address, tunnel_prefix, tunnel_dns, tunnel_mtu, last_seen, created_at)
VALUES ($1, $2, $3, $4, $5, 'online', '10.0.0.1', 24, '10.0.0.2', 1380, NOW(), NOW())
RETURNING *
"#,
)
@@ -192,4 +195,22 @@ impl AppRepository for PgRepository {
.fetch_one(&self.pool)
.await
}
// 🔥 ДОБАВЛЕНО: Методы
async fn get_subscription(&self, user_id: Uuid) -> Result<Option<Subscription>, sqlx::Error> {
sqlx::query_as::<_, Subscription>("SELECT plan_name, expires_at FROM subscriptions WHERE user_id = $1 AND expires_at > NOW()")
.bind(user_id)
.fetch_optional(&self.pool)
.await
}
async fn get_referral_stats(&self, user_id: Uuid) -> Result<(i64, i64), sqlx::Error> {
let row: (Option<i64>, Option<i64>) = sqlx::query_as(
"SELECT COUNT(*), SUM(earned_bonus) FROM referrals WHERE referrer_id = $1",
)
.bind(user_id)
.fetch_one(&self.pool)
.await?;
Ok((row.0.unwrap_or(0), row.1.unwrap_or(0)))
}
}