diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml index e148f01c..de86d419 100644 --- a/.github/workflows/docker-hub.yml +++ b/.github/workflows/docker-hub.yml @@ -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="v2.9.1-$(git rev-parse --short HEAD)" + VERSION="v2.9.2-$(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="v2.9.1-dev-$(git rev-parse --short HEAD)" + VERSION="v2.9.2-dev-$(git rev-parse --short HEAD)" TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:dev,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}" echo "🧪 Собираем dev версию: $VERSION" else - VERSION="v2.9.1-pr-$(git rev-parse --short HEAD)" + VERSION="v2.9.2-pr-$(git rev-parse --short HEAD)" TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:pr-$(git rev-parse --short HEAD)" echo "🔀 Собираем PR версию: $VERSION" fi diff --git a/.github/workflows/docker-registry.yml b/.github/workflows/docker-registry.yml index 1e85f153..7ae15e95 100644 --- a/.github/workflows/docker-registry.yml +++ b/.github/workflows/docker-registry.yml @@ -49,13 +49,13 @@ jobs: VERSION=${GITHUB_REF#refs/tags/} echo "🏷️ Building release version: $VERSION" elif [[ $GITHUB_REF == refs/heads/main ]]; then - VERSION="v2.9.1-$(git rev-parse --short HEAD)" + VERSION="v2.9.2-$(git rev-parse --short HEAD)" echo "🚀 Building main version: $VERSION" elif [[ $GITHUB_REF == refs/heads/dev ]]; then - VERSION="v2.9.1-dev-$(git rev-parse --short HEAD)" + VERSION="v2.9.2-dev-$(git rev-parse --short HEAD)" echo "🧪 Building dev version: $VERSION" else - VERSION="v2.9.1-pr-$(git rev-parse --short HEAD)" + VERSION="v2.9.2-pr-$(git rev-parse --short HEAD)" echo "🔀 Building PR version: $VERSION" fi echo "version=$VERSION" >> $GITHUB_OUTPUT diff --git a/Dockerfile b/Dockerfile index bde8e2da..47ec74e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN pip install --no-cache-dir --upgrade pip && \ FROM python:3.13-slim -ARG VERSION="v2.9.1" +ARG VERSION="v2.9.2" ARG BUILD_DATE ARG VCS_REF diff --git a/LICENSE b/LICENSE index f25c12fb..a65fdef6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,28 @@ +"Commons Clause" License Condition v1.0 + +The Software is provided to you by the Licensor under the License, +as defined below, subject to the following condition. + +Without limiting other conditions in the License, the grant of rights +under the License will not include, and the License does not grant to +you, the right to Sell the Software. + +For purposes of the foregoing, "Sell" means practicing any or all of +the rights granted to you under the License to provide to third parties, +for a fee or other consideration (including without limitation fees for +hosting or consulting/support services related to the Software), a +product or service whose value derives, entirely or substantially, from +the functionality of the Software. + +Any license notice or attribution required by the License must also +include this Commons Clause License Condition notice. + +Software: remnawave-bedolaga-telegram-bot +License: MIT +Licensor: Fr1ngg + +--- + MIT License Copyright (c) 2025 Fr1ngg diff --git a/app/webapi/routes/subscriptions.py b/app/webapi/routes/subscriptions.py index 4cbd8346..6979ec79 100644 --- a/app/webapi/routes/subscriptions.py +++ b/app/webapi/routes/subscriptions.py @@ -8,6 +8,8 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload +from app.services.subscription_service import SubscriptionService + from app.config import settings from app.database.crud.server_squad import get_random_trial_squad_uuid from app.database.crud.subscription import ( @@ -143,70 +145,92 @@ async def create_subscription( if not settings.is_devices_selection_enabled(): forced_devices = settings.get_disabled_mode_device_limit() - if payload.is_trial: - trial_device_limit = payload.device_limit - if trial_device_limit is None: - trial_device_limit = forced_devices - duration_days = payload.duration_days or settings.TRIAL_DURATION_DAYS - traffic_limit_gb = payload.traffic_limit_gb or settings.TRIAL_TRAFFIC_LIMIT_GB + subscription = None + try: + if payload.is_trial: + trial_device_limit = payload.device_limit + if trial_device_limit is None: + trial_device_limit = forced_devices + duration_days = payload.duration_days or settings.TRIAL_DURATION_DAYS + traffic_limit_gb = payload.traffic_limit_gb or settings.TRIAL_TRAFFIC_LIMIT_GB - if existing: - connected_squads = await _choose_trial_squads( - db, payload.squad_uuid, list(existing.connected_squads or []) - ) - subscription = await replace_subscription( - db, - existing, - duration_days=duration_days, - traffic_limit_gb=traffic_limit_gb, - device_limit=( - trial_device_limit - if trial_device_limit is not None - else settings.TRIAL_DEVICE_LIMIT - ), - connected_squads=connected_squads, - is_trial=True, - update_server_counters=True, - ) - else: - subscription = await create_trial_subscription( - db, - user_id=payload.user_id, - duration_days=duration_days, - traffic_limit_gb=traffic_limit_gb, - device_limit=trial_device_limit, - squad_uuid=payload.squad_uuid, - ) - else: - if payload.duration_days is None: - raise HTTPException(status.HTTP_400_BAD_REQUEST, "duration_days is required for paid subscriptions") - device_limit = payload.device_limit - if device_limit is None: - if forced_devices is not None: - device_limit = forced_devices + if existing: + connected_squads = await _choose_trial_squads( + db, payload.squad_uuid, list(existing.connected_squads or []) + ) + subscription = await replace_subscription( + db, + existing, + duration_days=duration_days, + traffic_limit_gb=traffic_limit_gb, + device_limit=( + trial_device_limit + if trial_device_limit is not None + else settings.TRIAL_DEVICE_LIMIT + ), + connected_squads=connected_squads, + is_trial=True, + update_server_counters=True, + ) else: - device_limit = settings.DEFAULT_DEVICE_LIMIT - if existing: - subscription = await replace_subscription( - db, - existing, - duration_days=payload.duration_days, - traffic_limit_gb=payload.traffic_limit_gb or settings.DEFAULT_TRAFFIC_LIMIT_GB, - device_limit=device_limit, - connected_squads=payload.connected_squads or [], - is_trial=False, - update_server_counters=True, - ) + subscription = await create_trial_subscription( + db, + user_id=payload.user_id, + duration_days=duration_days, + traffic_limit_gb=traffic_limit_gb, + device_limit=trial_device_limit, + squad_uuid=payload.squad_uuid, + ) else: - subscription = await create_paid_subscription( - db, - user_id=payload.user_id, - duration_days=payload.duration_days, - traffic_limit_gb=payload.traffic_limit_gb or settings.DEFAULT_TRAFFIC_LIMIT_GB, - device_limit=device_limit, - connected_squads=payload.connected_squads or [], - update_server_counters=True, - ) + if payload.duration_days is None: + raise HTTPException(status.HTTP_400_BAD_REQUEST, "duration_days is required for paid subscriptions") + device_limit = payload.device_limit + if device_limit is None: + if forced_devices is not None: + device_limit = forced_devices + else: + device_limit = settings.DEFAULT_DEVICE_LIMIT + if existing: + subscription = await replace_subscription( + db, + existing, + duration_days=payload.duration_days, + traffic_limit_gb=payload.traffic_limit_gb or settings.DEFAULT_TRAFFIC_LIMIT_GB, + device_limit=device_limit, + connected_squads=payload.connected_squads or [], + is_trial=False, + update_server_counters=True, + ) + else: + subscription = await create_paid_subscription( + db, + user_id=payload.user_id, + duration_days=payload.duration_days, + traffic_limit_gb=payload.traffic_limit_gb or settings.DEFAULT_TRAFFIC_LIMIT_GB, + device_limit=device_limit, + connected_squads=payload.connected_squads or [], + update_server_counters=True, + ) + + subscription_service = SubscriptionService() + rem_user = await subscription_service.create_remnawave_user( + db, + subscription, + reset_traffic=False + ) + if not rem_user: + raise ValueError("Failed to create/update user in Remnawave") + + await db.refresh(subscription) + + except HTTPException: + raise + except Exception as e: + try: + await db.rollback() + except Exception: + logger.exception("Rollback failed after error: %s", e) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to sync with Remnawave: {str(e)}") subscription = await _get_subscription(db, subscription.id) return _serialize_subscription(subscription)