RPU v2, promo/bank система, админка, оптимизации
— RPU v2: бюджет удачи, потолок (ceiling), комбек после проигрышей, hot/cold анализ, интеграция с promo-фазами — Bank API: mock-банк для карты (депозит/вывод с балансом 1000₽) — Promo-сервис: автогенерация промокодов каждый час, типы (card_to_site, luck_boost, ceiling_boost, free_case, reset_streak) — Promo-фазы: LUCKY/NEUTRAL/UNLUCKY, переключение каждые 1-6ч — Admin: история транзакций (user_history), управление промокодами, управление promo-фазами, API фильтров/коллекций, RPU info — Database: WAL mode, новые поля RPU v2, TransactionLog, карточный баланс, статистики (streaks, ceiling, budget) — Frontend: /deposit, /promo/activate, /withdraw, /card-balance, /online-count, /rpu-info, lifespan вместо startup event — UI: user_history.html, улучшения профиля, кейсов, навигации — Cases: обновление cases.json (1892 строки новых данных) — Achievements: кэш коллекций, оптимизация is_custom проверок — Sounds: дополнительные звуковые эффекты — Upgrade: интеграция с RPU (множители, streaks) — .gitignore: исключены .db, .bak, __pycache__
This commit is contained in:
+201
-3
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean, DateTime, ForeignKey, JSON, Text
|
||||
from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean, DateTime, ForeignKey, JSON, Text, text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, relationship
|
||||
from datetime import datetime
|
||||
@@ -10,6 +10,14 @@ engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
# Включаем WAL mode для параллельных чтений и уменьшения SQLITE_BUSY
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("PRAGMA journal_mode=WAL"))
|
||||
conn.execute(text("PRAGMA synchronous=NORMAL"))
|
||||
conn.execute(text("PRAGMA cache_size=-64000"))
|
||||
conn.execute(text("PRAGMA busy_timeout=5000"))
|
||||
conn.commit()
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
@@ -22,12 +30,19 @@ class User(Base):
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
last_login = Column(DateTime, nullable=True)
|
||||
|
||||
# Карта (кэш, источник правды — Bank API)
|
||||
card_balance_cached = Column(Float, default=1000.0)
|
||||
total_deposited = Column(Float, default=0.0) # всего закинуто card→site
|
||||
total_withdrawn = Column(Float, default=0.0) # всего выведено site→card
|
||||
last_deposit_date = Column(DateTime, nullable=True)
|
||||
|
||||
inventory_items = relationship("InventoryItem", back_populates="user", cascade="all, delete-orphan")
|
||||
case_openings = relationship("CaseOpening", back_populates="user", cascade="all, delete-orphan")
|
||||
contracts = relationship("Contract", back_populates="user", cascade="all, delete-orphan")
|
||||
rpu_settings = relationship("UserRPU", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
||||
upgrades = relationship("Upgrade", back_populates="user", cascade="all, delete-orphan")
|
||||
upgrade_rpu = relationship("UpgradeRPU", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
||||
transactions = relationship("TransactionLog", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
class UpgradeRPU(Base):
|
||||
__tablename__ = "upgrade_rpu"
|
||||
@@ -46,6 +61,12 @@ class UpgradeRPU(Base):
|
||||
total_success = Column(Integer, default=0)
|
||||
total_spent_value = Column(Float, default=0.0) # Стоимость потерянных предметов
|
||||
|
||||
# Серии
|
||||
current_win_streak = Column(Integer, default=0)
|
||||
current_lose_streak = Column(Integer, default=0)
|
||||
best_win_streak = Column(Integer, default=0)
|
||||
worst_lose_streak = Column(Integer, default=0)
|
||||
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
@@ -92,10 +113,41 @@ class UserRPU(Base):
|
||||
auto_adjust = Column(Boolean, default=False)
|
||||
|
||||
# Статистика для авто-подкрутки
|
||||
total_spent = Column(Float, default=0.0) # Всего потрачено
|
||||
total_opened = Column(Integer, default=0) # Всего открыто кейсов
|
||||
total_spent = Column(Float, default=0.0)
|
||||
total_opened = Column(Integer, default=0)
|
||||
total_value_received = Column(Float, default=0.0)
|
||||
last_adjustment = Column(DateTime, nullable=True)
|
||||
|
||||
# Серии
|
||||
current_streak = Column(Integer, default=0)
|
||||
best_streak = Column(Integer, default=0)
|
||||
worst_streak = Column(Integer, default=0)
|
||||
|
||||
# Последние результаты открытий
|
||||
last_results = Column(String(500), default="")
|
||||
|
||||
# ── RPU v2: Session (сессии) ──
|
||||
luck_budget = Column(Float, default=100.0) # 0-100, остаток удачи на сессию
|
||||
session_spent = Column(Float, default=0.0) # потрачено в текущей сессии
|
||||
session_won = Column(Float, default=0.0) # выиграно (ценность) в сессии
|
||||
session_reset_date = Column(DateTime, nullable=True) # когда сброшена сессия
|
||||
|
||||
# ── RPU v2: Ceiling (потолок) ──
|
||||
ceiling_multiplier = Column(Float, default=1.0) # временный множитель потолка (0.5-3.0)
|
||||
ceiling_break_count = Column(Integer, default=0) # сколько раз пробивал потолок
|
||||
ceiling_break_session = Column(Integer, default=0) # пробитий за сессию
|
||||
|
||||
# ── RPU v2: Comeback (комбек) ──
|
||||
consecutive_loss_value = Column(Float, default=0.0) # стоимость последовательных проигрышей
|
||||
consecutive_loss_count = Column(Integer, default=0)
|
||||
comeback_active = Column(Boolean, default=False)
|
||||
comeback_openings_left = Column(Integer, default=0)
|
||||
comeback_multiplier = Column(Float, default=1.0) # множитель удачи в режиме комбека
|
||||
|
||||
# ── RPU v2: Hot/Cold анализ ──
|
||||
hot_score = Column(Float, default=0.0) # >0 = горячий, <0 = холодный
|
||||
last_activity_date = Column(DateTime, nullable=True)
|
||||
|
||||
# Мета-данные
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
@@ -103,6 +155,40 @@ class UserRPU(Base):
|
||||
user = relationship("User", back_populates="rpu_settings")
|
||||
|
||||
|
||||
class PromoCode(Base):
|
||||
__tablename__ = "promo_codes"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
code = Column(String(50), unique=True, nullable=False, index=True)
|
||||
reward_type = Column(String(30), nullable=False) # card_to_site, luck_boost, ceiling_boost, free_case, reset_streak
|
||||
reward_amount = Column(Float, default=0.0)
|
||||
reward_data = Column(String(500), default="") # доп. параметры (JSON)
|
||||
max_uses = Column(Integer, default=1)
|
||||
used_count = Column(Integer, default=0)
|
||||
start_date = Column(DateTime, default=datetime.utcnow)
|
||||
end_date = Column(DateTime, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
created_by = Column(Integer, nullable=True)
|
||||
|
||||
|
||||
class TransactionLog(Base):
|
||||
__tablename__ = "transaction_log"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
tx_type = Column(String(30), nullable=False) # card_to_site, site_to_card, promo_bonus, admin
|
||||
amount = Column(Float, default=0.0)
|
||||
fee = Column(Float, default=0.0)
|
||||
promo_code = Column(String(50), nullable=True)
|
||||
item_name = Column(String(200), nullable=True) # при выводе скина
|
||||
inventory_item_id = Column(Integer, nullable=True)
|
||||
details = Column(String(500), default="")
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
user = relationship("User", back_populates="transactions")
|
||||
|
||||
|
||||
class InventoryItem(Base):
|
||||
__tablename__ = "inventory_items"
|
||||
|
||||
@@ -117,6 +203,8 @@ class InventoryItem(Base):
|
||||
obtained_from = Column(String(100)) # "case: название" или "contract"
|
||||
obtained_at = Column(DateTime, default=datetime.utcnow)
|
||||
is_equipped = Column(Boolean, default=False)
|
||||
image_url = Column(String(500), default="")
|
||||
price_rub = Column(Float, default=0.0)
|
||||
|
||||
user = relationship("User", back_populates="inventory_items")
|
||||
|
||||
@@ -196,6 +284,30 @@ class ActivityFeed(Base):
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Миграция: добавляем недостающие индексы для производительности
|
||||
try:
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
indices_to_add = {
|
||||
"inventory_items": ["user_id"],
|
||||
"case_openings": ["user_id", "opened_at"],
|
||||
"upgrades": ["user_id"],
|
||||
"contracts": ["user_id"],
|
||||
"activity_feed": ["user_id"],
|
||||
"user_achievements": ["user_id", "achievement_id"],
|
||||
}
|
||||
with engine.connect() as conn:
|
||||
for tbl, cols in indices_to_add.items():
|
||||
existing = [ix["name"] for ix in inspector.get_indexes(tbl)]
|
||||
for col in cols:
|
||||
ix_name = f"ix_{tbl}_{col}"
|
||||
if ix_name not in existing:
|
||||
conn.execute(text(f"CREATE INDEX IF NOT EXISTS {ix_name} ON {tbl} ({col})"))
|
||||
conn.commit()
|
||||
print("[DB] Индексы: OK")
|
||||
except Exception as e:
|
||||
print(f"[DB] Миграция индексов: {e}")
|
||||
|
||||
# Миграция: добавляем колонку hidden если её нет
|
||||
try:
|
||||
from sqlalchemy import inspect
|
||||
@@ -209,6 +321,92 @@ try:
|
||||
except Exception as e:
|
||||
print(f"[DB] Миграция hidden: {e}")
|
||||
|
||||
# Миграция: добавляем image_url и price_rub в inventory_items
|
||||
try:
|
||||
from sqlalchemy import text
|
||||
inspector = inspect(engine)
|
||||
cols = [c["name"] for c in inspector.get_columns("inventory_items")]
|
||||
if "image_url" not in cols:
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE inventory_items ADD COLUMN image_url VARCHAR(500) DEFAULT ''"))
|
||||
conn.execute(text("ALTER TABLE inventory_items ADD COLUMN price_rub FLOAT DEFAULT 0.0"))
|
||||
conn.commit()
|
||||
# Backfill existing rows from ALL_ITEMS
|
||||
try:
|
||||
from backend import ALL_ITEMS
|
||||
with engine.connect() as conn:
|
||||
rows = conn.execute(text("SELECT id, item_id FROM inventory_items WHERE image_url = '' OR price_rub = 0.0")).fetchall()
|
||||
for row_id, item_id in rows:
|
||||
if 0 <= item_id < len(ALL_ITEMS):
|
||||
item = ALL_ITEMS[item_id]
|
||||
img = item.get("image_url", "")
|
||||
pr = item.get("price_rub", 0.0)
|
||||
conn.execute(
|
||||
text("UPDATE inventory_items SET image_url = :img, price_rub = :pr WHERE id = :rid"),
|
||||
{"img": img, "pr": pr, "rid": row_id}
|
||||
)
|
||||
conn.commit()
|
||||
print(f"[DB] Backfilled {len(rows)} inventory_items from ALL_ITEMS")
|
||||
except Exception as e2:
|
||||
print(f"[DB] Backfill inventory_items: {e2}")
|
||||
except Exception as e:
|
||||
print(f"[DB] Миграция inventory_items: {e}")
|
||||
|
||||
# Миграция: новые поля в users (карта)
|
||||
try:
|
||||
inspector = inspect(engine)
|
||||
cols = [c["name"] for c in inspector.get_columns("users")]
|
||||
if "card_balance_cached" not in cols:
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN card_balance_cached FLOAT DEFAULT 1000.0"))
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN total_deposited FLOAT DEFAULT 0.0"))
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN total_withdrawn FLOAT DEFAULT 0.0"))
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN last_deposit_date TIMESTAMP"))
|
||||
conn.commit()
|
||||
print("[DB] Миграция users (card): OK")
|
||||
except Exception as e:
|
||||
print(f"[DB] Миграция users card: {e}")
|
||||
|
||||
# Миграция: новые поля в user_rpu (v2)
|
||||
try:
|
||||
inspector = inspect(engine)
|
||||
cols = [c["name"] for c in inspector.get_columns("user_rpu")]
|
||||
for col, coltype in [
|
||||
("luck_budget", "FLOAT DEFAULT 100.0"),
|
||||
("session_spent", "FLOAT DEFAULT 0.0"),
|
||||
("session_won", "FLOAT DEFAULT 0.0"),
|
||||
("session_reset_date", "TIMESTAMP"),
|
||||
("ceiling_multiplier", "FLOAT DEFAULT 1.0"),
|
||||
("ceiling_break_count", "INTEGER DEFAULT 0"),
|
||||
("ceiling_break_session", "INTEGER DEFAULT 0"),
|
||||
("consecutive_loss_value", "FLOAT DEFAULT 0.0"),
|
||||
("consecutive_loss_count", "INTEGER DEFAULT 0"),
|
||||
("comeback_active", "BOOLEAN DEFAULT 0"),
|
||||
("comeback_openings_left", "INTEGER DEFAULT 0"),
|
||||
("comeback_multiplier", "FLOAT DEFAULT 1.0"),
|
||||
("hot_score", "FLOAT DEFAULT 0.0"),
|
||||
("last_activity_date", "TIMESTAMP"),
|
||||
]:
|
||||
if col not in cols:
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text(f"ALTER TABLE user_rpu ADD COLUMN {col} {coltype}"))
|
||||
conn.commit()
|
||||
print(f"[DB] Миграция user_rpu: {col}")
|
||||
except Exception as e:
|
||||
print(f"[DB] Миграция user_rpu: {e}")
|
||||
|
||||
# Миграция: создаём PromoCode если нет
|
||||
try:
|
||||
Base.metadata.tables["promo_codes"].create(bind=engine, checkfirst=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Миграция: создаём TransactionLog если нет
|
||||
try:
|
||||
Base.metadata.tables["transaction_log"].create(bind=engine, checkfirst=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user