trial updates
This commit is contained in:
+35
-14
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user