-- ===================================================================== -- РОЛЕВАЯ АДМИНКА: Owner / Moderator / Partner -- ===================================================================== -- Раньше единственным способом получить админ-доступ была привязка к -- ADMIN_TG_ID (см. AuthGuard::admin_guard) — один захардкоженный Telegram- -- аккаунт на весь деплой. Теперь роль полноценно хранится в БД, у -- Owner/Moderator/Partner есть логин+пароль (не Telegram/seed), а -- ADMIN_TG_ID выпиливается из кода вместе с этой миграцией. ALTER TABLE users DROP CONSTRAINT IF EXISTS users_role_check; ALTER TABLE users ADD CONSTRAINT users_role_check CHECK (role IN ('user', 'owner', 'moderator', 'partner', 'admin')); -- 'admin' оставлен в допустимых значениях только ради обратной -- совместимости с уже существующими строками — новый код его не -- присваивает нигде. ALTER TABLE users ADD COLUMN IF NOT EXISTS username VARCHAR(50) UNIQUE; ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT; -- Право модератора управлять нодами — переключается Owner'ом поштучно, -- а не зашито в саму роль (см. AuthGuard::node_manage_guard). ALTER TABLE users ADD COLUMN IF NOT EXISTS can_manage_nodes BOOLEAN NOT NULL DEFAULT FALSE; -- Фиксированный процент партнёра с продаж приведённых им покупателей. ALTER TABLE users ADD COLUMN IF NOT EXISTS commission_percent NUMERIC(5,2); -- Короткий код для диплинка t.me/?start= — префикс "p_" -- нужен боту, чтобы однозначно отличать партнёрский код от numeric -- referrer-tg_id и от auth_sessions.auth_code (см. bot.rs::Command::Start). ALTER TABLE users ADD COLUMN IF NOT EXISTS partner_code VARCHAR(20) UNIQUE; -- Постоянная привязка покупателя к партнёру, выставляется один раз при -- регистрации через диплинк (first-wins, см. set_referred_by_partner). ALTER TABLE users ADD COLUMN IF NOT EXISTS referred_by_partner_id UUID REFERENCES users(id); CREATE INDEX IF NOT EXISTS idx_users_referred_by_partner ON users(referred_by_partner_id) WHERE referred_by_partner_id IS NOT NULL; -- Леджер начислений — по одной записи на оплаченный инвойс приведённого -- покупателя, а не бегущий итог на одной строке (нужна история для вывода -- средств). Валюта — валюта самого инвойса, без конвертации (см. BillingService::confirm_payment). CREATE TABLE IF NOT EXISTS partner_commissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), partner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, currency VARCHAR(10) NOT NULL, amount BIGINT NOT NULL, percent_at_time NUMERIC(5,2) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(invoice_id) -- защита от повторного начисления при retry confirm_payment ); CREATE INDEX IF NOT EXISTS idx_partner_commissions_partner_currency ON partner_commissions(partner_id, currency); CREATE TABLE IF NOT EXISTS withdrawal_requests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), partner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, currency VARCHAR(10) NOT NULL, amount BIGINT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'paid')), note TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), processed_at TIMESTAMPTZ, processed_by UUID REFERENCES users(id) ); CREATE INDEX IF NOT EXISTS idx_withdrawal_requests_partner_status ON withdrawal_requests(partner_id, status); CREATE INDEX IF NOT EXISTS idx_withdrawal_requests_status ON withdrawal_requests(status);