Ролевая админка (Owner/Moderator/Partner) + оплата через @CryptoBot
Ролевая система вместо привязки к Telegram ---------------------------------------- Раньше единственный способ получить админ-доступ — admin_guard, пускавший по ADMIN_TG_ID или role=="admin" (без сеттера для роли). Заменяет на Owner/Moderator/Partner: - migrations/0005_roles_and_partners.sql — username/password_hash на users (NULL у обычных Ghost/Telegram-юзеров, тот же паттерн, что и у seed_hash/ tg_id), can_manage_nodes, commission_percent, partner_code, referred_by_partner_id; новые таблицы partner_commissions и withdrawal_requests. - auth/guard.rs — admin_guard заменён на owner_guard/staff_guard/ node_manage_guard/partner_guard; ADMIN_TG_ID убран из ApiState и main.rs. - auth/service.rs — hash_password/login_staff со случайной Argon2-солью на каждого юзера (не общий фиксированный ARGON_SALT, который годится для высокоэнтропийных seed-логинов, но не для человеческих паролей). - POST /api/v1/auth/admin/login — вход модератора/партнёра/овнера паролем, переиспользует существующий issue_session(). - --create-owner CLI-флаг (main.rs) со скрытым вводом пароля (rpassword) — вместо ручного SQL для создания первого владельца с VPS-консоли. - src/modules/staff/ — эндпоинты создания модераторов/партнёров, переключения can_manage_nodes, списка заявок на вывод и их approve/reject/paid. - Комиссия партнёра начисляется в BillingService::confirm_payment отдельным шагом после существующей рефералки, пересчитывается через get_plan_price(invoice.plan_name, invoice.currency) — НЕ через invoice.amount напрямую, потому что у провайдеров разная внутренняя единица (TON — nanoTON+2% буфер, CryptoBot — фиатные центы напрямую). - bot.rs — /start p_XXXXXX привязывает покупателя к партнёру по коду (first-wins, до существующих веток numeric-tg_id-рефералки и кода входа). - frontend/ — AdminLogin/AdminLayout/StaffPage/WithdrawalsPage/EarningsPage поверх существующего Vite-админ-фронта (без отдельного проекта). Оплата через @CryptoBot ------------------------ - modules/billing/providers/cryptobot.rs — новый PaymentProvider (Crypto Pay API), плюс попутно исправлены два реальных бага в существующем коде: confirm_payment был жёстко завязан на TON-провайдера при любом инвойсе, и InvoiceRecord не хранил currency, из-за чего TON-курс молча применялся ко всем провайдерам вместо родной валюты инвойса. - bot.rs — экран выбора способа оплаты для CryptoBot-инвойсов. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+8
-3
@@ -31,6 +31,7 @@ use crate::modules::{
|
||||
nodes::{controller as node_ctrl, service::NodeService},
|
||||
proxy_internal,
|
||||
referrals::{controller as ref_ctrl, service::ReferralService},
|
||||
staff::{controller as staff_ctrl, service::StaffService},
|
||||
users::{controller as user_ctrl, service::UserService},
|
||||
};
|
||||
|
||||
@@ -41,9 +42,9 @@ pub struct ApiState {
|
||||
pub node_service: NodeService,
|
||||
pub referral_service: ReferralService,
|
||||
pub user_service: UserService,
|
||||
pub staff_service: StaffService,
|
||||
pub jwt_secret: String,
|
||||
pub bot_token: String,
|
||||
pub admin_tg_id: i64,
|
||||
/// Юзернейм бота (без `@`) — отдаётся фронту через `GET /api/v1/config`.
|
||||
/// Не публичный build-time env (`VITE_BOT_USERNAME`): фронт — статический
|
||||
/// SPA-бандл, собираемый один раз в CI и одинаковый что для прод-, что для
|
||||
@@ -82,9 +83,9 @@ impl Clone for ApiState {
|
||||
node_service: self.node_service.clone(),
|
||||
referral_service: self.referral_service.clone(),
|
||||
user_service: self.user_service.clone(),
|
||||
staff_service: self.staff_service.clone(),
|
||||
jwt_secret: self.jwt_secret.clone(),
|
||||
bot_token: self.bot_token.clone(),
|
||||
admin_tg_id: self.admin_tg_id,
|
||||
bot_username: self.bot_username.clone(),
|
||||
cookie_domain: self.cookie_domain.clone(),
|
||||
cookie_secure: self.cookie_secure,
|
||||
@@ -139,7 +140,11 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router {
|
||||
|
||||
let admin_routes_v1 = Router::new()
|
||||
.nest("/nodes", node_ctrl::admin_router(state.clone()))
|
||||
.nest("/users", user_ctrl::admin_router(state.clone()));
|
||||
.nest("/users", user_ctrl::admin_router(state.clone()))
|
||||
.nest("/staff", staff_ctrl::router(state.clone()))
|
||||
.nest("/staff", staff_ctrl::owner_router(state.clone()))
|
||||
.nest("/staff", staff_ctrl::staff_router(state.clone()))
|
||||
.nest("/partners", staff_ctrl::partner_router(state.clone()));
|
||||
|
||||
// Внутренний контракт для прокси-нод — отдельный секрет, не auth_guard
|
||||
// (прокси не пользовательская сессия) и не участвует в CORS ниже (никогда
|
||||
|
||||
Reference in New Issue
Block a user