Merge pull request #2331 from BEDOLAGA-DEV/main

w
This commit is contained in:
Egor
2026-01-18 22:14:34 +03:00
committed by GitHub
8 changed files with 63 additions and 28 deletions

View File

@@ -214,7 +214,7 @@ TRIAL_ACTIVATION_PRICE=0
# Сколько устройств доступно по дефолту при покупке платной подписки
DEFAULT_DEVICE_LIMIT=3
# Максимум устройств достопных к покупке (0 = Нет лимита)
# Максимум устройств доступных к покупке (0 = Нет лимита)
MAX_DEVICES_LIMIT=15
# Дефолт параметры для подписок выданных через админку

View File

@@ -36,15 +36,15 @@ jobs:
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:latest,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}"
echo "🏷️ Собираем релизную версию: $VERSION"
elif [[ $GITHUB_REF == refs/heads/main ]]; then
VERSION="v3.0.0-$(git rev-parse --short HEAD)"
VERSION="v3.1.0-$(git rev-parse --short HEAD)"
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:latest,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}"
echo "🚀 Собираем версию из main: $VERSION"
elif [[ $GITHUB_REF == refs/heads/dev ]]; then
VERSION="v3.0.0-dev-$(git rev-parse --short HEAD)"
VERSION="v3.1.0-dev-$(git rev-parse --short HEAD)"
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:dev,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}"
echo "🧪 Собираем dev версию: $VERSION"
else
VERSION="v3.0.0-pr-$(git rev-parse --short HEAD)"
VERSION="v3.1.0-pr-$(git rev-parse --short HEAD)"
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:pr-$(git rev-parse --short HEAD)"
echo "🔀 Собираем PR версию: $VERSION"
fi

View File

@@ -49,13 +49,13 @@ jobs:
VERSION=${GITHUB_REF#refs/tags/}
echo "🏷️ Building release version: $VERSION"
elif [[ $GITHUB_REF == refs/heads/main ]]; then
VERSION="v3.0.0-$(git rev-parse --short HEAD)"
VERSION="v3.1.0-$(git rev-parse --short HEAD)"
echo "🚀 Building main version: $VERSION"
elif [[ $GITHUB_REF == refs/heads/dev ]]; then
VERSION="v3.0.0-dev-$(git rev-parse --short HEAD)"
VERSION="v3.1.0-dev-$(git rev-parse --short HEAD)"
echo "🧪 Building dev version: $VERSION"
else
VERSION="v3.0.0-pr-$(git rev-parse --short HEAD)"
VERSION="v3.1.0-pr-$(git rev-parse --short HEAD)"
echo "🔀 Building PR version: $VERSION"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT

View File

@@ -14,7 +14,7 @@ RUN pip install --no-cache-dir --upgrade pip && \
FROM python:3.13-slim
ARG VERSION="v3.0.0"
ARG VERSION="v3.1.0"
ARG BUILD_DATE
ARG VCS_REF

View File

@@ -42,7 +42,8 @@ class ContestInfo(BaseModel):
slug: str
name: str
description: Optional[str] = None
prize_days: int
prize_type: str
prize_value: str
is_available: bool
already_played: bool = False
@@ -65,7 +66,8 @@ class ContestResult(BaseModel):
"""Result of contest attempt."""
is_winner: bool
message: str
prize_days: Optional[int] = None
prize_type: Optional[str] = None
prize_value: Optional[str] = None
# ============ Helpers ============
@@ -80,19 +82,47 @@ def _user_allowed(subscription) -> bool:
}
async def _award_prize(db: AsyncSession, user_id: int, prize_days: int) -> str:
async def _award_prize(db: AsyncSession, user_id: int, prize_type: str, prize_value: str) -> str:
"""Award prize to winner."""
subscription = await get_subscription_by_user_id(db, user_id)
if not subscription:
return "Error: subscription not found"
if prize_type == "days":
try:
days = int(prize_value)
except ValueError:
return "Error: invalid prize value"
subscription.end_date = subscription.end_date + timedelta(days=prize_days)
subscription.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(subscription)
subscription = await get_subscription_by_user_id(db, user_id)
if not subscription:
return "Error: subscription not found"
logger.info(f"🎁 Extended subscription for user {user_id} by {prize_days} days (contest prize)")
return f"Subscription extended by {prize_days} days"
subscription.end_date = subscription.end_date + timedelta(days=days)
subscription.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(subscription)
logger.info(f"🎁 Extended subscription for user {user_id} by {days} days (contest prize)")
return f"Subscription extended by {days} days"
elif prize_type == "balance":
from app.database.crud.user import get_user_by_id
try:
amount = float(prize_value)
except ValueError:
return "Error: invalid prize value"
user = await get_user_by_id(db, user_id)
if not user:
return "Error: user not found"
user.balance += amount
await db.commit()
await db.refresh(user)
logger.info(f"🎁 Added {amount} to balance for user {user_id} (contest prize)")
return f"Balance increased by {amount}"
else:
logger.warning(f"Unknown prize type: {prize_type}")
return f"Prize type '{prize_type}' not supported"
# ============ Routes ============
@@ -169,7 +199,8 @@ async def get_contests(
slug=tpl_slug,
name=rnd.template.name if rnd.template else tpl_slug,
description=rnd.template.description if rnd.template else None,
prize_days=rnd.template.prize_days if rnd.template else 0,
prize_type=rnd.template.prize_type if rnd.template else "days",
prize_value=rnd.template.prize_value if rnd.template else "1",
is_available=True,
already_played=attempt is not None,
))
@@ -368,11 +399,12 @@ async def submit_contest_answer(
if is_winner:
await increment_winner_count(db, round_obj)
prize_text = await _award_prize(db, user.id, tpl.prize_days)
prize_text = await _award_prize(db, user.id, tpl.prize_type, tpl.prize_value)
return ContestResult(
is_winner=True,
message=f"🎉 Congratulations! You won! {prize_text}",
prize_days=tpl.prize_days,
prize_type=tpl.prize_type,
prize_value=tpl.prize_value,
)
else:
lose_messages = {

View File

@@ -194,8 +194,8 @@ async def manual_start_round(
return
# Проверяем, есть ли уже активный раунд для этого шаблона
from app.database.crud.contest import get_active_rounds
exists = await get_active_rounds(db, tpl.id)
from app.database.crud.contest import get_active_round_by_template
exists = await get_active_round_by_template(db, tpl.id)
if exists:
await callback.answer(texts.t("ADMIN_ROUND_ALREADY_ACTIVE", "Раунд уже активен."), show_alert=True)
await show_daily_contest(callback, db_user, db)

View File

@@ -571,11 +571,14 @@ def get_daily_contest_manage_keyboard(
InlineKeyboardButton(text=_t(texts, "ADMIN_CONTEST_START_MANUAL", "🧪 Ручной старт"), callback_data=f"admin_daily_manual_{template_id}"),
],
[
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_PRIZE", "🏅 Приз (дни)"), callback_data=f"admin_daily_edit_{template_id}_prize_days"),
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_MAX_WINNERS", "👥 Победителей"), callback_data=f"admin_daily_edit_{template_id}_max_winners"),
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_PRIZE_TYPE", "🏅 Тип приза"), callback_data=f"admin_daily_edit_{template_id}_prize_type"),
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_PRIZE_VALUE", "💰 Значение приза"), callback_data=f"admin_daily_edit_{template_id}_prize_value"),
],
[
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_MAX_WINNERS", "👥 Победителей"), callback_data=f"admin_daily_edit_{template_id}_max_winners"),
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_ATTEMPTS", "🔁 Попытки"), callback_data=f"admin_daily_edit_{template_id}_attempts_per_user"),
],
[
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_TIMES", "⏰ Раундов/день"), callback_data=f"admin_daily_edit_{template_id}_times_per_day"),
],
[

View File

@@ -6,7 +6,7 @@
- `GET /contests/daily/templates?enabled_only=false` — список шаблонов игр.
- `GET /contests/daily/templates/{id}` — получить шаблон.
- `PATCH /contests/daily/templates/{id}` — обновить поля: `name`, `description`, `prize_days`, `max_winners`, `attempts_per_user`, `times_per_day`, `schedule_times`, `cooldown_hours`, `payload` (dict), `is_enabled`.
- `PATCH /contests/daily/templates/{id}` — обновить поля: `name`, `description`, `prize_type`, `prize_value`, `max_winners`, `attempts_per_user`, `times_per_day`, `schedule_times`, `cooldown_hours`, `payload` (dict), `is_enabled`.
- `POST /contests/daily/templates/{id}/start-round` — запустить раунд вручную. Тело:
```json
{