==================== FILE: README.md ==================== # Breakout Zone Backend This is the Django backend for the Breakout Zone project, which provides a REST API for game and server data. ## Setup Instructions 1. Install Python dependencies: ``` pip install django djangorestframework django-cors-headers ``` 2. Navigate to the backend directory: ``` cd backend ``` 3. Run migrations to set up the database: ``` python manage.py migrate ``` 4. Load initial mock data: ``` python manage.py load_mock_data ``` 5. Create a superuser for the admin interface: ``` python manage.py createsuperuser ``` 6. Start the development server: ``` python manage.py runserver ``` ## API Endpoints - `GET /api/data/` - Returns all games and servers - `GET /api/games/` - Returns all games - `GET /api/servers/` - Returns all servers ## Admin Interface Access the Django admin interface at `http://localhost:8000/admin/` to manage games and servers. ## Database The project uses SQLite for development. The database file is located at `db.sqlite3`. ==================== FILE: add_dayz_products.py ==================== import os import sys import django # Add the project directory to the Python path sys.path.append(os.path.dirname(os.path.abspath(__file__))) # Set the Django settings module os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'breakout_zone.settings') # Setup Django django.setup() from game_servers.models import Game, Product def add_dayz_products(): try: # Get the DayZ game dayz_game = Game.objects.get(title='DayZ') # Create some products for DayZ products_data = [ { 'name': 'Premium Currency Pack', 'description': '1000 premium coins for DayZ', 'price': 9.99, 'image_url': 'https://picsum.photos/seed/dayz-coins/300/300', 'is_active': True }, { 'name': 'VIP Status', 'description': 'VIP status for 30 days with exclusive benefits', 'price': 19.99, 'image_url': 'https://picsum.photos/seed/dayz-vip/300/300', 'is_active': True }, { 'name': 'Character Slot', 'description': 'Additional character slot for your account', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/dayz-character/300/300', 'is_active': True }, { 'name': 'Starter Pack', 'description': 'Essential items to start your survival journey', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/dayz-starter/300/300', 'is_active': True } ] # Add products to the database for product_data in products_data: product, created = Product.objects.get_or_create( name=product_data['name'], game=dayz_game, defaults=product_data ) if created: print(f"Created product: {product.name}") else: print(f"Product already exists: {product.name}") print(f"\nTotal products for DayZ: {Product.objects.filter(game=dayz_game).count()}") except Game.DoesNotExist: print("DayZ game not found in database") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": add_dayz_products() ==================== FILE: breakout_zone\__init__.py ==================== ==================== FILE: breakout_zone\asgi.py ==================== import os from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'breakout_zone.settings') application = get_asgi_application() ==================== FILE: breakout_zone\settings.py ==================== from pathlib import Path import os from dotenv import load_dotenv load_dotenv() BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-fallback-key-for-dev-only') DEBUG = os.getenv('DEBUG', '1') == '1' ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'breakoutzone.ru', 'www.breakoutzone.ru'] if os.getenv('ALLOWED_HOST'): ALLOWED_HOSTS.append(os.getenv('ALLOWED_HOST')) USE_X_FORWARDED_HOST = True SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'whitenoise.runserver_nostatic', 'django.contrib.staticfiles', 'corsheaders', 'django.contrib.sites', 'rest_framework', 'rest_framework.authtoken', 'social_django', 'game_servers', ] SOCIAL_AUTH_URL_NAMESPACE = 'social' MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'social_django.middleware.SocialAuthExceptionMiddleware', ] ROOT_URLCONF = 'breakout_zone.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'game_servers/templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], }, }, ] WSGI_APPLICATION = 'breakout_zone.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',}, {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',}, {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',}, {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',}, ] LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True STATIC_URL = 'static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' FRONTEND_URL = os.getenv('FRONTEND_URL', 'http://localhost:4173/') CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:4173", "http://127.0.0.1:4173", "https://breakoutzone.ru", "http://breakoutzone.ru", ] if FRONTEND_URL not in CORS_ALLOWED_ORIGINS: CORS_ALLOWED_ORIGINS.append(FRONTEND_URL) CSRF_TRUSTED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:4173", "http://127.0.0.1:4173", "https://breakoutzone.ru", "http://breakoutzone.ru", ] if FRONTEND_URL not in CSRF_TRUSTED_ORIGINS: CSRF_TRUSTED_ORIGINS.append(FRONTEND_URL) CORS_ALLOW_CREDENTIALS = True SESSION_COOKIE_SECURE = not DEBUG CSRF_COOKIE_SECURE = not DEBUG CSRF_COOKIE_SAMESITE = 'Lax' SESSION_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_HTTPONLY = False REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ], 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ], } AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', 'social_core.backends.steam.SteamOpenId', 'social_core.backends.vk.VKOAuth2', 'social_core.backends.telegram.TelegramAuth', ] SOCIAL_AUTH_PIPELINE = [ 'social_core.pipeline.social_auth.social_details', 'social_core.pipeline.social_auth.social_uid', 'social_core.pipeline.social_auth.auth_allowed', 'social_core.pipeline.social_auth.social_user', 'game_servers.pipeline.check_for_conflict', 'social_core.pipeline.user.get_username', 'social_core.pipeline.social_auth.associate_by_email', 'social_core.pipeline.user.create_user', 'social_core.pipeline.social_auth.associate_user', 'game_servers.pipeline.save_profile', 'social_core.pipeline.social_auth.load_extra_data', 'social_core.pipeline.user.user_details', ] SOCIAL_AUTH_ON_AUTH_ASSOCIATED = 'game_servers.views.social_error_view' SOCIAL_AUTH_STEAM_API_KEY = os.getenv('SOCIAL_AUTH_STEAM_API_KEY', 'ВАШ_STEAM_API_KEY') SOCIAL_AUTH_STEAM_EXTRA_DATA = ['player'] SOCIAL_AUTH_VK_OAUTH2_KEY = os.getenv('SOCIAL_AUTH_VK_OAUTH2_KEY', 'ВАШ_VK_APP_ID') SOCIAL_AUTH_VK_OAUTH2_SECRET = os.getenv('SOCIAL_AUTH_VK_OAUTH2_SECRET', 'ВАШ_VK_APP_SECRET') SOCIAL_AUTH_TELEGRAM_BOT_TOKEN = os.getenv('SOCIAL_AUTH_TELEGRAM_BOT_TOKEN', 'ВАШ_ТОКЕН_ТЕЛЕГРАМ_БОТА') LOGIN_REDIRECT_URL = '/auth/complete/token/' SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = f'{FRONTEND_URL}#/profile' LOGOUT_REDIRECT_URL = FRONTEND_URL SITE_ID = 1 ==================== FILE: breakout_zone\test_steam_link.py ==================== import requests # URL, по которому фронтенд обращается для начала аутентификации через Steam # Убедитесь, что ваш Django-сервер запущен на порту 8000 DJANGO_LOGIN_URL = "http://localhost:8000/oauth/login/steam/" print(f"▶️ Пытаемся получить URL для редиректа с {DJANGO_LOGIN_URL}") try: # Важный параметр `allow_redirects=False` запрещает `requests` автоматически # переходить по ссылке редиректа. Вместо этого он вернет нам сам ответ с кодом 302. response = requests.get(DJANGO_LOGIN_URL, allow_redirects=False) print(f" Статус ответа от сервера: {response.status_code}") # Ожидаемый успешный сценарий: статус 3xx (обычно 302) if 300 <= response.status_code < 400: redirect_url = response.headers.get('Location') print("\n✅ УСПЕХ: Django-сервер корректно отдает команду на перенаправление.") print(f" URL для перенаправления: {redirect_url}") if redirect_url and 'steamcommunity.com' in redirect_url: print("\n 🎉 Отлично! Это валидный URL для входа через Steam.") else: print("\n ⚠️ ВНИМАНИЕ: URL не похож на ссылку Steam. Проверьте настройки SOCIAL_AUTH_STEAM_API_KEY.") # Если сервер отдает 200 OK, значит он не делает редирект, а отдает страницу. # Это и есть причина "зависания" попапа. elif response.status_code == 200: print("\n❌ ОШИБКА: Сервер вернул статус 200 OK вместо редиректа.") print(" Это означает, что social-django не выполняет перенаправление. Наиболее вероятные причины:") print(" 1. Конфликт в URL-конфигурации.") print(" 2. Настройка `LOGIN_URL` в `settings.py` перехватывает запрос.") print(" 3. Неправильно настроены `AUTHENTICATION_BACKENDS`.") else: print(f"\n❌ ОШИБКА: Сервер вернул неожиданный статус {response.status_code}.") print(" Содержимое ответа:") print(response.text) except requests.exceptions.ConnectionError: print("\n❌ ОШИБКА: Не удалось подключиться к серверу.") print(" Убедитесь, что ваш Django-сервер запущен на http://localhost:8000") print(" (команда: python backend/manage.py runserver)") except Exception as e: print(f"\n❌ Произошла непредвиденная ошибка: {e}") ==================== FILE: breakout_zone\urls.py ==================== from django.contrib import admin from django.urls import path, include from game_servers.views import ( game_data, update_server_status, news_list, forum_categories, forum_topics, forum_posts, user_profile, products_by_game, products_by_server, apply_promo_code, create_ticket, update_user_status, get_online_users, get_chat_messages, send_chat_message, register_user, login_user, logout_user, social_error_view, products_by_category, categories_list, auth_complete_token_sender ) # Добавляем импорт представлений из social_django, чтобы ссылаться на них напрямую from social_django import views as social_views # --- НОВЫЙ БЛОК --- # Мы создаем список URL-шаблонов для social_django вручную. # Это позволяет нам добавить недостающий путь для 'associate'. social_auth_patterns = [ # URL для НАЧАЛА входа (когда пользователь не залогинен) -> /oauth/login/steam/ path('login//', social_views.auth, name='begin'), # URL, на который провайдер (Steam) возвращает пользователя ПОСЛЕ аутентификации path('complete//', social_views.complete, name='complete'), # !!! ВОТ РЕШЕНИЕ !!! # URL для НАЧАЛА ПРИВЯЗКИ (когда пользователь уже залогинен) -> /oauth/associate/steam/ # Он использует то же самое представление `auth`, что и `login`, но сам факт вызова # через этот URL запускает логику привязки. path('associate//', social_views.auth, name='associate_begin'), # URL для отвязки аккаунта path('disconnect//', social_views.disconnect, name='disconnect'), path('disconnect///', social_views.disconnect, name='disconnect_individual'), ] urlpatterns = [ path('admin/', admin.site.urls), # Все ваши API пути остаются без изменений path('api/data/', game_data, name='game_data'), path('api/update-status/', update_server_status, name='update_server_status'), path('api/news/', news_list, name='news_list'), path('api/forum-categories/', forum_categories, name='forum_categories'), path('api/forum-topics//', forum_topics, name='forum_topics'), path('api/forum-posts//', forum_posts, name='forum_posts'), path('api/user/profile/', user_profile, name='user_profile'), path('api/products/category//', products_by_category), path('api/categories/', categories_list), path('api/products//', products_by_game), path('api/products/server//', products_by_server), path('api/promo-codes/apply/', apply_promo_code), path('api/tickets/create/', create_ticket), path('api/user/status/update/', update_user_status), path('api/user/online/', get_online_users), path('api/chat/messages/', get_chat_messages), path('api/chat/send/', send_chat_message), path('api/auth/register/', register_user), path('api/auth/login/', login_user), path('api/auth/logout/', logout_user), path('api/auth/', include('rest_framework.urls')), path('api/', include('game_servers.urls')), path('oauth/', include((social_auth_patterns, 'social_django'), namespace='social')), path('auth/complete/token/', auth_complete_token_sender, name='auth_complete_token_sender'), path('social-error/', social_error_view, name='social_error'), ] ==================== FILE: breakout_zone\wsgi.py ==================== import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'breakout_zone.settings') application = get_wsgi_application() ==================== FILE: game_servers\__init__.py ==================== ==================== FILE: game_servers\admin.py ==================== from django.contrib import admin from .models import Game, Server @admin.register(Game) class GameAdmin(admin.ModelAdmin): list_display = ('title', 'genre', 'status') list_filter = ('status', 'genre') search_fields = ('title', 'description') @admin.register(Server) class ServerAdmin(admin.ModelAdmin): list_display = ('name', 'game', 'region', 'current_players', 'max_players') list_filter = ('region', 'game') search_fields = ('name', 'ip_address') ==================== FILE: game_servers\apps.py ==================== from django.apps import AppConfig class GameServersConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'game_servers' ==================== FILE: game_servers\management\commands\add_category_products.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Game, Product, Category class Command(BaseCommand): help = 'Add sample products organized by categories' def handle(self, *args, **options): # Get or create DayZ game dayz_game, created = Game.objects.get_or_create( title='DayZ', defaults={ 'genre': 'Mil-Sim Survival', 'description': 'Survive a post-apocalyptic world where every other player can be friend or foe. Unscripted moments and intense player interactions.', 'cover_image_url': 'https://picsum.photos/seed/dayz-chernarus/1200/400', 'status': 'live', 'general_donation_link': '/donate/game/1' } ) if created: self.stdout.write(f"Created game: {dayz_game.title}") else: self.stdout.write(f"Found game: {dayz_game.title}") # Create or get categories categories_data = [ {'name': 'Cosmetics', 'description': 'Cosmetic items and skins'}, {'name': 'Utility', 'description': 'Utility items and services'}, {'name': 'Membership', 'description': 'Membership and VIP status'}, {'name': 'Game Items', 'description': 'In-game items and equipment'}, {'name': 'Currency', 'description': 'Premium currency and coins'}, {'name': 'Account', 'description': 'Account-related items'} ] categories = {} for cat_data in categories_data: category, created = Category.objects.get_or_create( name=cat_data['name'], game=dayz_game, defaults={'description': cat_data['description']} ) categories[cat_data['name']] = category if created: self.stdout.write(f"Created category: {category.name}") else: self.stdout.write(f"Found category: {category.name}") # Create products for each category products_data = [ # Cosmetics { 'name': 'Premium Skin Pack', 'description': 'Exclusive cosmetic items for your character', 'price': 9.99, 'image_url': 'https://picsum.photos/seed/skin-pack/300/300', 'category': 'Cosmetics' }, { 'name': 'Weapon Camouflage', 'description': 'Special camouflage patterns for your weapons', 'price': 7.99, 'image_url': 'https://picsum.photos/seed/camo/300/300', 'category': 'Cosmetics' }, # Utility { 'name': 'Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/queue/300/300', 'category': 'Utility' }, { 'name': 'Experience Boost', 'description': '2x experience gain for 24 hours', 'price': 2.99, 'image_url': 'https://picsum.photos/seed/xp-boost/300/300', 'category': 'Utility' }, # Membership { 'name': 'VIP Status', 'description': 'VIP status for 30 days with exclusive benefits', 'price': 19.99, 'image_url': 'https://picsum.photos/seed/vip/300/300', 'category': 'Membership' }, { 'name': 'Premium Membership', 'description': 'Premium membership for 30 days with all benefits', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/premium/300/300', 'category': 'Membership' }, # Game Items { 'name': 'Medical Kit', 'description': 'Advanced medical supplies for emergency situations', 'price': 1.99, 'image_url': 'https://picsum.photos/seed/medical/300/300', 'category': 'Game Items' }, { 'name': 'Ammunition Pack', 'description': 'Large ammunition pack for extended operations', 'price': 3.99, 'image_url': 'https://picsum.photos/seed/ammo/300/300', 'category': 'Game Items' }, # Currency { 'name': 'Premium Currency Pack', 'description': '1000 premium coins for DayZ', 'price': 9.99, 'image_url': 'https://picsum.photos/seed/coins/300/300', 'category': 'Currency' }, { 'name': 'Large Coin Bundle', 'description': '5000 premium coins for DayZ', 'price': 39.99, 'image_url': 'https://picsum.photos/seed/coins-large/300/300', 'category': 'Currency' }, # Account { 'name': 'Character Slot', 'description': 'Additional character slot for your DayZ account', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/character/300/300', 'category': 'Account' }, { 'name': 'Name Change', 'description': 'Change your character name', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/name-change/300/300', 'category': 'Account' } ] # Create or update products for product_data in products_data: product, created = Product.objects.update_or_create( name=product_data['name'], game=dayz_game, defaults={ 'description': product_data['description'], 'price': product_data['price'], 'image_url': product_data['image_url'], 'is_active': True, 'category': product_data['category'] } ) if created: self.stdout.write(f"Created product: {product.name} in category {product.category}") else: self.stdout.write(f"Updated product: {product.name} in category {product.category}") self.stdout.write( self.style.SUCCESS('Successfully added/updated category-based products') ) ==================== FILE: game_servers\management\commands\add_dayz_products.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Game, Product class Command(BaseCommand): help = 'Add sample products for DayZ game' def handle(self, *args, **options): try: # Get the DayZ game dayz_game = Game.objects.get(title='DayZ') # Create some products for DayZ with categories products_data = [ { 'name': 'Premium Currency Pack', 'description': '1000 premium coins for DayZ', 'price': 9.99, 'image_url': 'https://picsum.photos/seed/dayz-coins/300/300', 'is_active': True, 'category': 'Currency' }, { 'name': 'VIP Status', 'description': 'VIP status for 30 days with exclusive benefits', 'price': 19.99, 'image_url': 'https://picsum.photos/seed/dayz-vip/300/300', 'is_active': True, 'category': 'Premium' }, { 'name': 'Character Slot', 'description': 'Additional character slot for your account', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/dayz-character/300/300', 'is_active': True, 'category': 'Account' }, { 'name': 'Starter Pack', 'description': 'Essential items to start your survival journey', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/dayz-starter/300/300', 'is_active': True, 'category': 'Starter' } ] # Add products to the database for product_data in products_data: product, created = Product.objects.get_or_create( name=product_data['name'], game=dayz_game, defaults=product_data ) if created: self.stdout.write( self.style.SUCCESS(f'Created product: {product.name} in category {product_data["category"]}') ) else: # Update existing product with category product.category = product_data['category'] product.save() self.stdout.write( self.style.WARNING(f'Updated product: {product.name} with category {product_data["category"]}') ) self.stdout.write( self.style.SUCCESS(f'\nTotal products for DayZ: {Product.objects.filter(game=dayz_game).count()}') ) except Game.DoesNotExist: self.stdout.write( self.style.ERROR('DayZ game not found in database') ) except Exception as e: self.stdout.write( self.style.ERROR(f'Error: {e}') ) ==================== FILE: game_servers\management\commands\add_server_products.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Server, Product class Command(BaseCommand): help = 'Add server-specific products for DayZ servers' def handle(self, *args, **options): # Server-specific products data with categories server_products_data = { # CHERNO server (101) 101: [ { 'name': 'Cherno Exclusive Skin Pack', 'description': 'Exclusive cosmetic items for Chernarus server', 'price': 7.99, 'image_url': 'https://picsum.photos/seed/cherno-skin/300/300', 'is_active': True, 'category': 'Cosmetics' }, { 'name': 'Cherno Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/cherno-queue/300/300', 'is_active': True, 'category': 'Utility' }, { 'name': 'Cherno VIP Status', 'description': 'VIP status for 30 days with exclusive benefits on Chernarus server', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/cherno-vip/300/300', 'is_active': True, 'category': 'Membership' } ], # LIVONIA server (102) 102: [ { 'name': 'Livonia Exclusive Weapon Skins', 'description': 'Exclusive weapon skins for Livonia server', 'price': 6.99, 'image_url': 'https://picsum.photos/seed/livonia-weapons/300/300', 'is_active': True, 'category': 'Cosmetics' }, { 'name': 'Livonia Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/livonia-queue/300/300', 'is_active': True, 'category': 'Utility' }, { 'name': 'Livonia VIP Status', 'description': 'VIP status for 30 days with exclusive benefits on Livonia server', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/livonia-vip/300/300', 'is_active': True, 'category': 'Membership' } ], # NAMALSK server (103) 103: [ { 'name': 'Namalsk Survival Kit', 'description': 'Essential survival items for Namalsk server', 'price': 8.99, 'image_url': 'https://picsum.photos/seed/namalsk-kit/300/300', 'is_active': True, 'category': 'Game Items' }, { 'name': 'Namalsk Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/namalsk-queue/300/300', 'is_active': True, 'category': 'Utility' }, { 'name': 'Namalsk VIP Status', 'description': 'VIP status for 30 days with exclusive benefits on Namalsk server', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/namalsk-vip/300/300', 'is_active': True, 'category': 'Membership' } ], # TAKISTAN server (104) 104: [ { 'name': 'Takistan Military Pack', 'description': 'Military-grade equipment for Takistan server', 'price': 9.99, 'image_url': 'https://picsum.photos/seed/takistan-military/300/300', 'is_active': True, 'category': 'Game Items' }, { 'name': 'Takistan Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/takistan-queue/300/300', 'is_active': True, 'category': 'Utility' }, { 'name': 'Takistan VIP Status', 'description': 'VIP status for 30 days with exclusive benefits on Takistan server', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/takistan-vip/300/300', 'is_active': True, 'category': 'Membership' } ], # SAKHAL server (105) 105: [ { 'name': 'Sakhal Winter Survival Pack', 'description': 'Specialized winter gear for Sakhal server', 'price': 7.99, 'image_url': 'https://picsum.photos/seed/sakhal-winter/300/300', 'is_active': True, 'category': 'Game Items' }, { 'name': 'Sakhal Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/sakhal-queue/300/300', 'is_active': True, 'category': 'Utility' }, { 'name': 'Sakhal VIP Status', 'description': 'VIP status for 30 days with exclusive benefits on Sakhal server', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/sakhal-vip/300/300', 'is_active': True, 'category': 'Membership' } ], # PROJECT RUSSIA server (201) 201: [ { 'name': 'Russia Themed Cosmetic Pack', 'description': 'Russia-themed cosmetic items for Project Russia server', 'price': 6.99, 'image_url': 'https://picsum.photos/seed/russia-cosmetic/300/300', 'is_active': True, 'category': 'Cosmetics' }, { 'name': 'Russia Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 4.99, 'image_url': 'https://picsum.photos/seed/russia-queue/300/300', 'is_active': True, 'category': 'Utility' }, { 'name': 'Russia VIP Status', 'description': 'VIP status for 30 days with exclusive benefits on Project Russia server', 'price': 14.99, 'image_url': 'https://picsum.photos/seed/russia-vip/300/300', 'is_active': True, 'category': 'Membership' } ], # CS2 Classic server (301) 301: [ { 'name': 'Classic Weapon Skins', 'description': 'Classic weapon skins for CS2 Classic server', 'price': 5.99, 'image_url': 'https://picsum.photos/seed/cs2-classic/300/300', 'is_active': True, 'category': 'Cosmetics' }, { 'name': 'Classic Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 3.99, 'image_url': 'https://picsum.photos/seed/cs2-queue/300/300', 'is_active': True, 'category': 'Utility' } ], # CS2 AWP Lego 2 server (302) 302: [ { 'name': 'AWP Lego Exclusive Skins', 'description': 'Exclusive AWP Lego themed skins', 'price': 7.99, 'image_url': 'https://picsum.photos/seed/awp-lego/300/300', 'is_active': True, 'category': 'Cosmetics' }, { 'name': 'AWP Lego Priority Queue', 'description': 'Skip the queue and join the server faster', 'price': 3.99, 'image_url': 'https://picsum.photos/seed/awp-queue/300/300', 'is_active': True, 'category': 'Utility' } ] } # Create or update products for each server for server_id, products_data in server_products_data.items(): try: server = Server.objects.get(id=server_id) self.stdout.write(f"Adding products for server: {server.name}") for product_data in products_data: # Create or update product product, created = Product.objects.update_or_create( name=product_data['name'], game=server.game, defaults={ 'description': product_data['description'], 'price': product_data['price'], 'image_url': product_data['image_url'], 'is_active': product_data['is_active'], 'category': product_data['category'] } ) if created: self.stdout.write(f" Created product: {product.name}") else: self.stdout.write(f" Updated product: {product.name}") except Server.DoesNotExist: self.stdout.write(f"Server with ID {server_id} does not exist") self.stdout.write( self.style.SUCCESS('Successfully added/updated server-specific products') ) ==================== FILE: game_servers\management\commands\check_server_status.py ==================== import time import socket import requests from django.core.management.base import BaseCommand from game_servers.models import Server try: import a2s A2S_AVAILABLE = True except ImportError: A2S_AVAILABLE = False class Command(BaseCommand): help = 'Check server status and update player counts and ping' def handle(self, *args, **options): if not A2S_AVAILABLE: self.stdout.write(self.style.ERROR( 'ERROR: python-a2s library not installed!' )) self.stdout.write('Install it with: pip install python-a2s') return servers = Server.objects.all() for server in servers: self.stdout.write(f"--- Checking server: {server.name} ({server.ip_address}) ---") try: ip, port = server.ip_address.split(':') port = int(port) except ValueError: self.stdout.write(self.style.WARNING( f"Invalid IP format for server {server.name}: {server.ip_address}" )) continue # ИЗМЕНЕНО: Теперь получаем 4 значения, включая max_players is_online, ping, player_count, max_players = self.check_server(ip, port, server.game_id) if is_online: server.current_players = player_count server.ping = ping server.max_players = max_players # ДОБАВЛЕНО: Обновляем максимальное количество игроков server.save() self.stdout.write(self.style.SUCCESS( # ИЗМЕНЕНО: Используем обновленное значение max_players в логе f"✓ SUCCESS: Server {server.name} is online - Players: {player_count}/{max_players}, Ping: {ping}ms" )) else: server.current_players = 0 server.ping = 0 server.save() self.stdout.write(self.style.WARNING( f"✗ OFFLINE: Server {server.name} failed all checks." )) self.stdout.write(self.style.SUCCESS('Finished checking all servers')) def check_server(self, ip, port, game_id): """Main method to check server based on game type""" if game_id == 1: # DayZ return self.check_dayz_server(ip, port) elif game_id == 2: # Project Zomboid return self.check_zomboid_server(ip, port) elif game_id == 3: # CS2 return self.check_source_server(ip, port) else: # ИЗМЕНЕНО: Возвращаем 4 значения return False, 0, 0, 0 def find_query_port_via_steam_api(self, ip, game_port): """ Use Steam Web API to find the correct query port. This is the most reliable method if the server is listed correctly. """ self.stdout.write(" -> Strategy 1: Trying Steam Web API...") try: url = f"https://api.steampowered.com/ISteamApps/GetServersAtAddress/v1/?addr={ip}:{game_port}" # Note: Using HTTPS and the newer API endpoint if available. Let's stick to the user's for compatibility. url = f"http://api.steampowered.com/ISteamApps/GetServersAtAddress/v0001/?addr={ip}" response = requests.get(url, timeout=5) response.raise_for_status() data = response.json() if data.get('response', {}).get('servers'): for server in data['response']['servers']: if server.get('gameport') == game_port: query_port = server.get('steamid') # Note: Correction, query port is NOT in steamid. It is often not returned directly. # The query port is typically derived from the addr field, but let's assume the user's logic was trying to find `queryport` query_port = server.get('queryport') # This key may not exist in this API call. # Let's try to parse the addr field as a fallback if not query_port and 'addr' in server: try: _, q_port_str = server['addr'].split(':') query_port = int(q_port_str) except (ValueError, IndexError): pass if query_port: self.stdout.write(self.style.SUCCESS(f" ✓ Steam API found query port: {query_port}")) return query_port self.stdout.write(" - Steam API did not return a query port.") except requests.RequestException as e: self.stdout.write(f" - Steam API lookup failed: {str(e)}") return None def probe_query_port(self, ip, base_port, max_range=20): """ Probe ports to find the query port using a more robust strategy. 1. Try common default query ports. 2. Try common DayZ port patterns/offsets. 3. Try a sequential range around the game port. """ ports_to_try = set() # 1. Add common default query ports for port in [27015, 27016, 27017, 27018, 27019, 27020]: ports_to_try.add(port) # 2. Add common DayZ patterns # Example: game_port=2302, query_port=27016 or game_port=2302, query_port=2303 common_offsets = [1, 2, 3, 4, 24714, 24715] for offset in common_offsets: ports_to_try.add(base_port + offset) # 3. Add a sequential range around the base port for offset in range(1, max_range + 1): ports_to_try.add(base_port + offset) self.stdout.write(f" -> Strategy 2: Probing {len(ports_to_try)} potential query ports...") for port in sorted(list(ports_to_try)): # Sort for logical probing order try: address = (ip, port) start_time = time.time() info = a2s.info(address, timeout=2.0) ping = int((time.time() - start_time) * 1000) self.stdout.write(self.style.SUCCESS(f" ✓ Found working query port: {port}")) # ИЗМЕНЕНО: Возвращаем 4 значения return port, info.player_count, info.max_players, ping except (socket.timeout, OSError): # This is expected, so we don't print an error for each failed port continue except Exception as e: # Log other unexpected errors self.stdout.write(f" - Error probing port {port}: {e}") continue self.stdout.write(" - Probing failed to find a working query port.") # ИЗМЕНЕНО: Возвращаем 4 значения return None, 0, 0, 0 def check_dayz_server(self, ip, port): """ Check DayZ server with multiple strategies 1. Try Steam API to get query port. 2. If that fails, probe a list of common and sequential ports. """ query_port = self.find_query_port_via_steam_api(ip, port) if query_port: try: address = (ip, query_port) start_time = time.time() info = a2s.info(address, timeout=4.0) ping = int((time.time() - start_time) * 1000) # ИЗМЕНЕНО: Возвращаем 4 значения return True, ping, info.player_count, info.max_players except (socket.timeout, OSError) as e: self.stdout.write(f" - Query port {query_port} from API failed: {str(e)}. Falling back to probing.") # ИЗМЕНЕНО: Распаковываем 4 значения found_port, player_count, max_players, ping = self.probe_query_port(ip, port) if found_port: # ИЗМЕНЕНО: Возвращаем 4 значения return True, ping, player_count, max_players # ИЗМЕНЕНО: Возвращаем 4 значения return False, 0, 0, 0 def check_zomboid_server(self, ip, port): """ Check Project Zomboid server with a robust, multi-strategy approach. 1. Try Steam API to find the exact query port (most reliable). 2. Fallback to the default convention (game_port + 1). """ self.stdout.write(" -> Zomboid: Starting multi-strategy check...") api_query_port = self.find_query_port_via_steam_api(ip, port) if api_query_port: try: self.stdout.write(f" -> Zomboid (API): Querying port {api_query_port}...") address = (ip, api_query_port) start_time = time.time() info = a2s.info(address, timeout=5.0) ping = int((time.time() - start_time) * 1000) # ИЗМЕНЕНО: Возвращаем 4 значения return True, ping, info.player_count, info.max_players except Exception as e: self.stdout.write(self.style.WARNING( f" - Zomboid (API): Port {api_query_port} failed: {str(e)}. Falling back." )) default_query_port = port + 1 if api_query_port == default_query_port: self.stdout.write(" - Default port was already tried via API and failed. Aborting.") # ИЗМЕНЕНО: Возвращаем 4 значения return False, 0, 0, 0 self.stdout.write(f" -> Zomboid (Default): Trying default port {default_query_port}...") try: address = (ip, default_query_port) start_time = time.time() info = a2s.info(address, timeout=5.0) ping = int((time.time() - start_time) * 1000) # ИЗМЕНЕНО: Возвращаем 4 значения return True, ping, info.player_count, info.max_players except Exception as e: self.stdout.write(self.style.ERROR( f" - Zomboid (Default): Port {default_query_port} also failed: {str(e)}" )) # ИЗМЕНЕНО: Возвращаем 4 значения return False, 0, 0, 0 def check_source_server(self, ip, port): """ Check CS2/Source engine server using python-a2s. The query port for Source games is usually the same as the game port. """ self.stdout.write(" -> Using standard A2S source query...") address = (ip, port) try: start_time = time.time() info = a2s.info(address, timeout=5.0) ping = int((time.time() - start_time) * 1000) player_count = info.player_count max_players = info.max_players # ДОБАВЛЕНО # ИЗМЕНЕНО: Возвращаем 4 значения return True, ping, player_count, max_players except Exception as e: self.stdout.write(self.style.ERROR( f" - Error checking CS2 server {ip}:{port} - {str(e)}" )) # ИЗМЕНЕНО: Возвращаем 4 значения return False, 0, 0, 0 ==================== FILE: game_servers\management\commands\load_mock_data.py ==================== import json from django.core.management.base import BaseCommand from game_servers.models import Game, Server class Command(BaseCommand): help = 'Load real server data into the database' def handle(self, *args, **options): Game.objects.all().delete() Server.objects.all().delete() games_data = [ { "id": 1, "title": "DayZ", "genre": "Mil-Sim Survival", "description": "Survive a post-apocalyptic world where every other player can be friend or foe. Unscripted moments and intense player interactions.", "cover_image_url": "https://picsum.photos/seed/dayz-chernarus/1200/400", "status": "live", "general_donation_link": "/donate/game/1" }, { "id": 2, "title": "Project Zomboid", "genre": "Survival RPG", "description": "An uncompromising zombie survival RPG. Scavenge, build, craft, and fight to survive in a vast, unforgiving sandbox.", "cover_image_url": "https://picsum.photos/seed/zomboid-survival/1200/400", "status": "live", "general_donation_link": "/donate/game/2" }, { "id": 3, "title": "Counter-Strike 2", "genre": "Tactical FPS", "description": "The long-awaited sequel to the classic tactical shooter. Precision gunplay, strategic depth, and a global competitive scene await.", "cover_image_url": "https://picsum.photos/seed/cs2-dust/1200/400", "status": "live", "general_donation_link": "/donate/game/3" } ] games = {} for game_data in games_data: game = Game.objects.create( id=game_data["id"], title=game_data["title"], genre=game_data["genre"], description=game_data["description"], cover_image_url=game_data["cover_image_url"], status=game_data["status"], general_donation_link=game_data["general_donation_link"] ) games[game.id] = game self.stdout.write(f"Created game: {game.title}") servers_data = [ {"id": 101, "game_id": 1, "name": "BREAKOUT | 3PP | PVP | CHERNO | VANILLA+", "ip_address": "31.129.204.199:2302", "map_name": "Chernarus", "map_image_url": "https://picsum.photos/seed/Chernarus/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 60, "ping": 0, "donation_link": "/donate/server/101"}, {"id": 102, "game_id": 1, "name": "BREAKOUT | 3PP | PVP | LIVONIA | VANILLA+", "ip_address": "31.129.204.199:2303", "map_name": "Livonia", "map_image_url": "https://picsum.photos/seed/Livonia/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 60, "ping": 0, "donation_link": "/donate/server/102"}, {"id": 103, "game_id": 1, "name": "BREAKOUT | 3PP | PVP | NAMALSK | VANILLA+", "ip_address": "31.129.204.199:2603", "map_name": "Namalsk", "map_image_url": "https://picsum.photos/seed/Namalsk/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 60, "ping": 0, "donation_link": "/donate/server/103"}, {"id": 104, "game_id": 1, "name": "BREAKOUT | 3PP | PVP | TAKISTAN | VANILLA+", "ip_address": "31.129.204.199:2311", "map_name": "Takistan", "map_image_url": "https://picsum.photos/seed/Takistan/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 60, "ping": 0, "donation_link": "/donate/server/104"}, {"id": 105, "game_id": 1, "name": "BREAKOUT | 3PP | PVP | SAKHAL | VANILLA+", "ip_address": "31.129.204.199:2310", "map_name": "Sakhal", "map_image_url": "https://picsum.photos/seed/Sakhal/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 60, "ping": 0, "donation_link": "/donate/server/105"}, {"id": 201, "game_id": 2, "name": "BREAKOUT | PVP | PROJECT RUSSIA", "ip_address": "31.129.204.199:16262", "map_name": "Project Russia", "map_image_url": "https://picsum.photos/seed/ProjectRussia/400/200", "region": "EU-West", "language": "RU", "current_players": 0, "max_players": 32, "ping": 0, "donation_link": "/donate/server/201"}, {"id": 301, "game_id": 3, "name": "CS2Breakout | Classic", "ip_address": "31.129.204.199:27015", "map_name": "de_mirage", "map_image_url": "https://picsum.photos/seed/de_mirage/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 24, "ping": 0, "donation_link": "/donate/server/301"}, {"id": 302, "game_id": 3, "name": "CS2Breakout | AWP Lego 2", "ip_address": "31.129.204.199:27035", "map_name": "awp_lego_2", "map_image_url": "https://picsum.photos/seed/awp_lego_2/400/200", "region": "EU-West", "language": "EN", "current_players": 0, "max_players": 24, "ping": 0, "donation_link": "/donate/server/302"}, ] for server_data in servers_data: server = Server.objects.create( id=server_data["id"], game=games[server_data["game_id"]], name=server_data["name"], ip_address=server_data["ip_address"], map_name=server_data["map_name"], map_image_url=server_data["map_image_url"], region=server_data["region"], language=server_data["language"], current_players=server_data["current_players"], max_players=server_data["max_players"], ping=server_data["ping"], donation_link=server_data["donation_link"] ) self.stdout.write(f"Created server: {server.name}") self.stdout.write( self.style.SUCCESS('Successfully loaded real server data') ) ==================== FILE: game_servers\management\commands\load_real_server_data.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Game, Server class Command(BaseCommand): help = 'Load real server data into the database' def handle(self, *args, **options): # Create or update games games_data = [ { 'id': 1, 'title': 'DayZ', 'genre': 'Mil-Sim Survival', 'description': 'Survive a post-apocalyptic world where every other player can be friend or foe. Unscripted moments and intense player interactions.', 'cover_image_url': 'https://picsum.photos/seed/dayz-chernarus/1200/400', 'status': 'live', 'general_donation_link': '/donate/game/1' }, { 'id': 2, 'title': 'Project Zomboid', 'genre': 'Survival RPG', 'description': 'An uncompromising zombie survival RPG. Scavenge, build, craft, and fight to survive in a vast, unforgiving sandbox.', 'cover_image_url': 'https://picsum.photos/seed/zomboid-survival/1200/400', 'status': 'soon', 'general_donation_link': '/donate/game/2' }, { 'id': 3, 'title': 'Counter-Strike 2', 'genre': 'Tactical FPS', 'description': 'The long-awaited sequel to the classic tactical shooter. Precision gunplay, strategic depth, and a global competitive scene await.', 'cover_image_url': 'https://picsum.photos/seed/cs2-dust/1200/400', 'status': 'live', # ИЗМЕНЕНО 'general_donation_link': '/donate/game/3' } ] for game_data in games_data: game, created = Game.objects.update_or_create( id=game_data['id'], defaults=game_data ) if created: self.stdout.write(f"Created game: {game.title}") else: self.stdout.write(f"Updated game: {game.title}") # Create or update servers servers_data = [ # DayZ servers { 'id': 101, 'name': 'BREAKOUT | 3PP | PVP | CHERNO | VANILLA+', 'ip_address': '31.129.204.199:2302', 'map_name': 'Chernarus', 'map_image_url': 'https://picsum.photos/seed/Chernarus/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/101', 'game_id': 1 }, { 'id': 102, 'name': 'BREAKOUT | 3PP | PVP | LIVONIA | VANILLA+', 'ip_address': '31.129.204.199:2303', 'map_name': 'Livonia', 'map_image_url': 'https://picsum.photos/seed/Livonia/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/102', 'game_id': 1 }, { 'id': 103, 'name': 'BREAKOUT | 3PP | PVP | NAMALSK | VANILLA+', 'ip_address': '31.129.204.199:2603', 'map_name': 'Namalsk', 'map_image_url': 'https://picsum.photos/seed/Namalsk/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/103', 'game_id': 1 }, { 'id': 104, 'name': 'BREAKOUT | 3PP | PVP | TAKISTAN | VANILLA+', 'ip_address': '31.129.204.199:2311', 'map_name': 'Takistan', 'map_image_url': 'https://picsum.photos/seed/Takistan/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/104', 'game_id': 1 }, { 'id': 105, 'name': 'BREAKOUT | 3PP | PVP | SAKHAL | VANILLA+', 'ip_address': '31.129.204.199:2310', 'map_name': 'Sakhal', 'map_image_url': 'https://picsum.photos/seed/Sakhal/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/105', 'game_id': 1 }, # Project Zomboid server { 'id': 201, 'name': 'BREAKOUT | PVP | PROJECT RUSSIA', 'ip_address': '31.129.204.199:16262', 'map_name': 'Project Russia', 'map_image_url': 'https://picsum.photos/seed/ProjectRussia/400/200', 'region': 'EU-West', 'language': 'RU', 'current_players': 0, 'max_players': 32, 'ping': 0, 'donation_link': '/donate/server/201', 'game_id': 2 }, # CS2 servers { 'id': 301, 'name': 'CS2Breakout | Classic', 'ip_address': '31.129.204.199:27015', 'map_name': 'de_mirage', 'map_image_url': 'https://picsum.photos/seed/de_mirage/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 24, 'ping': 0, 'donation_link': '/donate/server/301', 'game_id': 3 }, { 'id': 302, 'name': 'CS2Breakout | AWP Lego 2', 'ip_address': '31.129.204.199:27035', 'map_name': 'awp_lego_2', 'map_image_url': 'https://picsum.photos/seed/awp_lego_2/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 24, 'ping': 0, 'donation_link': '/donate/server/302', 'game_id': 3 } ] for server_data in servers_data: server, created = Server.objects.update_or_create( id=server_data['id'], defaults=server_data ) if created: self.stdout.write(f"Created server: {server.name}") else: self.stdout.write(f"Updated server: {server.name}") self.stdout.write( self.style.SUCCESS('Successfully loaded real server data') ) ==================== FILE: game_servers\management\commands\populate_initial_data.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Game, Server class Command(BaseCommand): help = 'Populate initial game and server data' def handle(self, *args, **options): # Create or update games games_data = [ { 'id': 1, 'title': 'DayZ', 'genre': 'Mil-Sim Survival', 'description': 'Survive a post-apocalyptic world where every other player can be friend or foe. Unscripted moments and intense player interactions.', 'cover_image_url': 'https://picsum.photos/seed/dayz-chernarus/1200/400', 'status': 'live', 'general_donation_link': '/donate/game/1' }, { 'id': 2, 'title': 'Project Zomboid', 'genre': 'Survival RPG', 'description': 'An uncompromising zombie survival RPG. Scavenge, build, craft, and fight to survive in a vast, unforgiving sandbox.', 'cover_image_url': 'https://picsum.photos/seed/zomboid-survival/1200/400', 'status': 'soon', 'general_donation_link': '/donate/game/2' }, { 'id': 3, 'title': 'Counter-Strike 2', 'genre': 'Tactical FPS', 'description': 'The long-awaited sequel to the classic tactical shooter. Precision gunplay, strategic depth, and a global competitive scene await.', 'cover_image_url': 'https://picsum.photos/seed/cs2-dust/1200/400', 'status': 'live', # ИЗМЕНЕНО 'general_donation_link': '/donate/game/3' } ] for game_data in games_data: game, created = Game.objects.get_or_create( id=game_data['id'], defaults=game_data ) if created: self.stdout.write( self.style.SUCCESS(f'Created game: {game.title}') ) else: # Update existing game for key, value in game_data.items(): setattr(game, key, value) game.save() self.stdout.write( self.style.WARNING(f'Updated game: {game.title}') ) # Create or update servers servers_data = [ { 'id': 101, 'name': 'BREAKOUT | 3PP | PVP | CHERNO | VANILLA+', 'ip_address': '31.129.204.199:2302', 'map_name': 'Chernarus', 'map_image_url': 'https://picsum.photos/seed/Chernarus/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/101', 'game_id': 1 }, { 'id': 102, 'name': 'BREAKOUT | 3PP | PVP | LIVONIA | VANILLA+', 'ip_address': '31.129.204.199:2303', 'map_name': 'Livonia', 'map_image_url': 'https://picsum.photos/seed/Livonia/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/102', 'game_id': 1 }, { 'id': 103, 'name': 'BREAKOUT | 3PP | PVP | NAMALSK | VANILLA+', 'ip_address': '31.129.204.199:2603', 'map_name': 'Namalsk', 'map_image_url': 'https://picsum.photos/seed/Namalsk/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/103', 'game_id': 1 }, { 'id': 104, 'name': 'BREAKOUT | 3PP | PVP | TAKISTAN | VANILLA+', 'ip_address': '31.129.204.199:2311', 'map_name': 'Takistan', 'map_image_url': 'https://picsum.photos/seed/Takistan/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/104', 'game_id': 1 }, { 'id': 105, 'name': 'BREAKOUT | 3PP | PVP | SAKHAL | VANILLA+', 'ip_address': '31.129.204.199:2310', 'map_name': 'Sakhal', 'map_image_url': 'https://picsum.photos/seed/Sakhal/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 60, 'ping': 0, 'donation_link': '/donate/server/105', 'game_id': 1 }, { 'id': 201, 'name': 'BREAKOUT | PVP | PROJECT RUSSIA', 'ip_address': '31.129.204.199:16262', 'map_name': 'Project Russia', 'map_image_url': 'https://picsum.photos/seed/ProjectRussia/400/200', 'region': 'EU-West', 'language': 'RU', 'current_players': 0, 'max_players': 32, 'ping': 0, 'donation_link': '/donate/server/201', 'game_id': 2 }, { 'id': 301, 'name': 'CS2Breakout | Classic', 'ip_address': '31.129.204.199:27015', 'map_name': 'de_mirage', 'map_image_url': 'https://picsum.photos/seed/de_mirage/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 24, 'ping': 0, 'donation_link': '/donate/server/301', 'game_id': 3 }, { 'id': 302, 'name': 'CS2Breakout | AWP Lego 2', 'ip_address': '31.129.204.199:27035', 'map_name': 'awp_lego_2', 'map_image_url': 'https://picsum.photos/seed/awp_lego_2/400/200', 'region': 'EU-West', 'language': 'EN', 'current_players': 0, 'max_players': 24, 'ping': 0, 'donation_link': '/donate/server/302', 'game_id': 3 } ] for server_data in servers_data: server, created = Server.objects.get_or_create( id=server_data['id'], defaults=server_data ) if created: self.stdout.write( self.style.SUCCESS(f'Created server: {server.name}') ) else: # Update existing server for key, value in server_data.items(): setattr(server, key, value) server.save() self.stdout.write( self.style.WARNING(f'Updated server: {server.name}') ) self.stdout.write( self.style.SUCCESS('Finished populating initial data') ) ==================== FILE: game_servers\management\commands\start_server_monitor.py ==================== import socket import time import threading from django.core.management.base import BaseCommand from game_servers.models import Server class Command(BaseCommand): help = 'Start server monitoring service' def add_arguments(self, parser): parser.add_argument( '--interval', type=int, default=60, help='Check interval in seconds (default: 60)', ) def handle(self, *args, **options): interval = options['interval'] self.stdout.write(f"Starting server monitoring service (interval: {interval}s)") self.stdout.write("Press Ctrl+C to stop") try: while True: self.check_all_servers() time.sleep(interval) except KeyboardInterrupt: self.stdout.write(self.style.SUCCESS('Monitoring service stopped')) def check_all_servers(self): self.stdout.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Checking servers...") servers = Server.objects.all() for server in servers: try: ip, port = server.ip_address.split(':') port = int(port) except ValueError: self.stdout.write(self.style.WARNING(f"Invalid IP format for server {server.name}: {server.ip_address}")) continue is_online, ping, player_count = self.check_server(ip, port, server.game.id) if is_online: server.current_players = player_count server.ping = ping server.save() self.stdout.write( f" ✓ {server.name} - Players: {player_count}, Ping: {ping}ms" ) else: server.current_players = 0 server.ping = 0 server.save() self.stdout.write( f" ✗ {server.name} - Offline" ) def check_server(self, ip, port, game_id): try: start_time = time.time() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) result = sock.connect_ex((ip, port)) end_time = time.time() if result == 0: ping = int((end_time - start_time) * 1000) sock.close() player_count = self.get_simulated_player_count(game_id) return True, ping, player_count else: sock.close() return False, 0, 0 except Exception as e: return False, 0, 0 def get_simulated_player_count(self, game_id): import random if game_id == 1: # DayZ return random.randint(0, 60) elif game_id == 2: # Project Zomboid return random.randint(0, 32) elif game_id == 3: # CS2 return random.randint(0, 24) else: return random.randint(0, 20) ==================== FILE: game_servers\management\commands\update_game_statuses.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Game class Command(BaseCommand): help = 'Update game statuses: set DayZ to live, others to soon' def handle(self, *args, **options): # Set all games to 'soon' first Game.objects.all().update(status='soon') # Set DayZ to 'live' try: dayz_game = Game.objects.get(title='DayZ') dayz_game.status = 'live' dayz_game.save() self.stdout.write( self.style.SUCCESS(f'Successfully updated DayZ status to live') ) except Game.DoesNotExist: self.stdout.write( self.style.WARNING('DayZ game not found in database') ) # Report results games = Game.objects.all() for game in games: self.stdout.write(f'{game.title}: {game.status}') ==================== FILE: game_servers\management\commands\update_server_donation_links.py ==================== from django.core.management.base import BaseCommand from game_servers.models import Server class Command(BaseCommand): help = 'Update server donation links for DayZ servers' def handle(self, *args, **options): # Server-specific donation links server_donation_links = { 101: '/donate/server/101', # CHERNO 102: '/donate/server/102', # LIVONIA 103: '/donate/server/103', # NAMALSK 104: '/donate/server/104', # TAKISTAN 105: '/donate/server/105', # SAKHAL 201: '/donate/server/201', # PROJECT RUSSIA 301: '/donate/server/301', # CS2 Classic 302: '/donate/server/302', # CS2 AWP Lego 2 } # Update donation links for all servers for server_id, donation_link in server_donation_links.items(): try: server = Server.objects.get(id=server_id) server.donation_link = donation_link server.save() self.stdout.write( self.style.SUCCESS(f'Updated donation link for server "{server.name}" (ID: {server_id})') ) except Server.DoesNotExist: self.stdout.write( self.style.WARNING(f'Server with ID {server_id} not found') ) self.stdout.write( self.style.SUCCESS('Finished updating server donation links') ) ==================== FILE: game_servers\migrations\0001_initial.py ==================== # Generated by Django 5.2.7 on 2025-11-04 18:36 import django.db.models.deletion from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Game', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), ('title', models.CharField(max_length=200)), ('genre', models.CharField(max_length=100)), ('description', models.TextField()), ('cover_image_url', models.URLField()), ('status', models.CharField(choices=[('live', 'Live'), ('soon', 'Soon')], max_length=10)), ('general_donation_link', models.URLField()), ], ), migrations.CreateModel( name='Server', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=200)), ('ip_address', models.CharField(max_length=50)), ('map_name', models.CharField(max_length=100)), ('map_image_url', models.URLField()), ('region', models.CharField(max_length=50)), ('language', models.CharField(max_length=10)), ('current_players', models.IntegerField()), ('max_players', models.IntegerField()), ('ping', models.IntegerField()), ('donation_link', models.URLField()), ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='servers', to='game_servers.game')), ], ), ] ==================== FILE: game_servers\migrations\0002_forumcategory_promocode_forumtopic_forumpost_news_and_more.py ==================== # Generated by Django 5.2.7 on 2025-11-04 19:15 import django.db.models.deletion from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('game_servers', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='ForumCategory', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), ('description', models.TextField(blank=True)), ('order', models.IntegerField(default=0)), ], options={ 'verbose_name_plural': 'Forum categories', }, ), migrations.CreateModel( name='PromoCode', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('code', models.CharField(max_length=50, unique=True)), ('discount_percentage', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True)), ('balance_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('is_active', models.BooleanField(default=True)), ('valid_from', models.DateTimeField()), ('valid_to', models.DateTimeField()), ('max_uses', models.IntegerField(default=1)), ('times_used', models.IntegerField(default=0)), ], ), migrations.CreateModel( name='ForumTopic', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('is_pinned', models.BooleanField(default=False)), ('is_locked', models.BooleanField(default=False)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topics', to='game_servers.forumcategory')), ], ), migrations.CreateModel( name='ForumPost', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('content', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posts', to='game_servers.forumtopic')), ], ), migrations.CreateModel( name='News', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200)), ('content', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('is_published', models.BooleanField(default=True)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='Product', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200)), ('description', models.TextField()), ('price', models.DecimalField(decimal_places=2, max_digits=10)), ('image_url', models.URLField(blank=True, null=True)), ('is_active', models.BooleanField(default=True)), ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='game_servers.game')), ], ), migrations.CreateModel( name='Order', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('quantity', models.IntegerField(default=1)), ('total_price', models.DecimalField(decimal_places=2, max_digits=10)), ('status', models.CharField(choices=[('pending', 'Pending'), ('paid', 'Paid'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending', max_length=20)), ('created_at', models.DateTimeField(auto_now_add=True)), ('paid_at', models.DateTimeField(blank=True, null=True)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='game_servers.product')), ], ), migrations.CreateModel( name='Ticket', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('subject', models.CharField(max_length=200)), ('content', models.TextField()), ('status', models.CharField(choices=[('open', 'Open'), ('in_progress', 'In Progress'), ('resolved', 'Resolved'), ('closed', 'Closed')], default='open', max_length=20)), ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('urgent', 'Urgent')], default='medium', max_length=20)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tickets', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='TicketMessage', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('content', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='game_servers.ticket')), ], ), migrations.CreateModel( name='UserProfile', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('role', models.CharField(choices=[('user', 'User'), ('helper', 'Helper'), ('moderator', 'Moderator'), ('admin', 'Admin'), ('tech_admin', 'Tech Admin'), ('creator', 'Creator')], default='user', max_length=20)), ('balance', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), ('avatar_url', models.URLField(blank=True, null=True)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='UserPromoCode', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('used_at', models.DateTimeField(auto_now_add=True)), ('promo_code', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='game_servers.promocode')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] ==================== FILE: game_servers\migrations\0003_chatmessage_useronlinestatus_userattendance.py ==================== # Generated by Django 5.2.7 on 2025-11-04 20:59 import django.db.models.deletion from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('game_servers', '0002_forumcategory_promocode_forumtopic_forumpost_news_and_more'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='ChatMessage', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('content', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('is_read', models.BooleanField(default=False)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'ordering': ['created_at'], }, ), migrations.CreateModel( name='UserOnlineStatus', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('is_online', models.BooleanField(default=False)), ('last_activity', models.DateTimeField(auto_now=True)), ('current_page', models.CharField(blank=True, max_length=200)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='UserAttendance', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date', models.DateField(auto_now_add=True)), ('login_count', models.IntegerField(default=1)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'unique_together': {('user', 'date')}, }, ), ] ==================== FILE: game_servers\migrations\0004_product_category.py ==================== # Generated by Django 5.2.7 on 2025-11-17 16:27 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('game_servers', '0003_chatmessage_useronlinestatus_userattendance'), ] operations = [ migrations.AddField( model_name='product', name='category', field=models.CharField(blank=True, max_length=100, null=True), ), ] ==================== FILE: game_servers\migrations\0005_category.py ==================== # Generated by Django 5.2.7 on 2025-11-18 10:28 import django.db.models.deletion from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('game_servers', '0004_product_category'), ] operations = [ migrations.CreateModel( name='Category', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), ('description', models.TextField(blank=True)), ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='categories', to='game_servers.game')), ], ), ] ==================== FILE: game_servers\migrations\__init__.py ==================== ==================== FILE: game_servers\models.py ==================== from django.db import models from django.contrib.auth.models import User class Game(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=200) genre = models.CharField(max_length=100) description = models.TextField() cover_image_url = models.URLField() status = models.CharField( max_length=10, choices=[ ('live', 'Live'), ('soon', 'Soon') ] ) general_donation_link = models.URLField() def __str__(self): return self.title class Server(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=200) ip_address = models.CharField(max_length=50) map_name = models.CharField(max_length=100) map_image_url = models.URLField() region = models.CharField(max_length=50) language = models.CharField(max_length=10) current_players = models.IntegerField() max_players = models.IntegerField() ping = models.IntegerField() donation_link = models.URLField() game = models.ForeignKey( Game, on_delete=models.CASCADE, related_name='servers' ) def __str__(self): return f"{self.name} ({self.game.title})" class UserProfile(models.Model): ROLE_CHOICES = [ ('user', 'User'), ('helper', 'Helper'), ('moderator', 'Moderator'), ('admin', 'Admin'), ('tech_admin', 'Tech Admin'), ('creator', 'Creator'), ] user = models.OneToOneField(User, on_delete=models.CASCADE) role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user') balance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) avatar_url = models.URLField(blank=True, null=True) def __str__(self): return f"{self.user.username}'s Profile" class News(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) is_published = models.BooleanField(default=True) def __str__(self): return self.title class ForumCategory(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) order = models.IntegerField(default=0) class Meta: verbose_name_plural = "Forum categories" def __str__(self): return self.name class ForumTopic(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey(User, on_delete=models.CASCADE) category = models.ForeignKey(ForumCategory, on_delete=models.CASCADE, related_name='topics') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) is_pinned = models.BooleanField(default=False) is_locked = models.BooleanField(default=False) def __str__(self): return self.title class ForumPost(models.Model): content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE) topic = models.ForeignKey(ForumTopic, on_delete=models.CASCADE, related_name='posts') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Post by {self.author.username} in {self.topic.title}" class Category(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name='categories') def __str__(self): return f"{self.game.title} - {self.name}" class Product(models.Model): name = models.CharField(max_length=200) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) image_url = models.URLField(blank=True, null=True) is_active = models.BooleanField(default=True) game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name='products') category = models.CharField(max_length=100, blank=True, null=True) # Keep as CharField for simplicity def __str__(self): return self.name class Order(models.Model): STATUS_CHOICES = [ ('pending', 'Pending'), ('paid', 'Paid'), ('completed', 'Completed'), ('cancelled', 'Cancelled'), ] user = models.ForeignKey(User, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField(default=1) total_price = models.DecimalField(max_digits=10, decimal_places=2) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') created_at = models.DateTimeField(auto_now_add=True) paid_at = models.DateTimeField(blank=True, null=True) def __str__(self): return f"Order {self.id} by {self.user.username}" class PromoCode(models.Model): code = models.CharField(max_length=50, unique=True) discount_percentage = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True) balance_amount = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) is_active = models.BooleanField(default=True) valid_from = models.DateTimeField() valid_to = models.DateTimeField() max_uses = models.IntegerField(default=1) times_used = models.IntegerField(default=0) def __str__(self): return self.code class UserPromoCode(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) promo_code = models.ForeignKey(PromoCode, on_delete=models.CASCADE) used_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.user.username} used {self.promo_code.code}" class Ticket(models.Model): STATUS_CHOICES = [ ('open', 'Open'), ('in_progress', 'In Progress'), ('resolved', 'Resolved'), ('closed', 'Closed'), ] PRIORITY_CHOICES = [ ('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('urgent', 'Urgent'), ] user = models.ForeignKey(User, on_delete=models.CASCADE) assigned_to = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, related_name='assigned_tickets') subject = models.CharField(max_length=200) content = models.TextField() status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open') priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.subject class TicketMessage(models.Model): ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name='messages') author = models.ForeignKey(User, on_delete=models.CASCADE) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Message by {self.author.username} on {self.ticket.subject}" class ChatMessage(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) is_read = models.BooleanField(default=False) class Meta: ordering = ['created_at'] def __str__(self): return f"Message by {self.author.username}" class UserOnlineStatus(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) is_online = models.BooleanField(default=False) last_activity = models.DateTimeField(auto_now=True) current_page = models.CharField(max_length=200, blank=True) def __str__(self): return f"{self.user.username} - {'Online' if self.is_online else 'Offline'}" class UserAttendance(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) date = models.DateField(auto_now_add=True) login_count = models.IntegerField(default=1) class Meta: unique_together = ('user', 'date') def __str__(self): return f"{self.user.username} - {self.date}" ==================== FILE: game_servers\pipeline.py ==================== # backend/game_servers/pipeline.py from .models import UserProfile def check_for_conflict(strategy, user, social, *args, **kwargs): """ Проверяет конфликт привязки: не пытается ли залогиненный пользователь привязать соц. аккаунт, который уже принадлежит другому. """ if user and social and user != social.user: from social_core.exceptions import AuthAlreadyAssociated raise AuthAlreadyAssociated(strategy.backend) def save_profile(backend, user, response, is_new=False, *args, **kwargs): """ Создает/обновляет UserProfile и ВСЕГДА синхронизирует аватар из Steam. """ profile, created = UserProfile.objects.get_or_create(user=user) if backend.name == 'steam': details = kwargs.get('details', {}) player_data = details.get('player', {}) avatar_url = player_data.get('avatarfull') # --- ГЛАВНОЕ ИСПРАВЛЕНИЕ --- # Мы больше не проверяем is_new или пустое ли поле. # Если от Steam пришел URL аватара, мы просто его сохраняем. # Это гарантирует, что аватар будет обновляться при каждом входе. if avatar_url: # Проверяем, нужно ли вообще обновлять, чтобы избежать лишних запросов к БД if profile.avatar_url != avatar_url: profile.avatar_url = avatar_url profile.save() ==================== FILE: game_servers\serializers.py ==================== from rest_framework import serializers from social_django.models import UserSocialAuth from .models import ( Game, Server, UserProfile, News, ForumCategory, ForumTopic, ForumPost, Product, Order, PromoCode, UserPromoCode, Ticket, TicketMessage, UserAttendance, UserOnlineStatus, ChatMessage ) from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'email', 'first_name', 'last_name'] class GameSerializer(serializers.ModelSerializer): class Meta: model = Game fields = '__all__' class ServerSerializer(serializers.ModelSerializer): class Meta: model = Server fields = '__all__' class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'email', 'first_name', 'last_name'] class UserProfileSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) # ДОБАВЛЕНО: новое поле для получения списка соц. сетей social_accounts = serializers.SerializerMethodField() class Meta: model = UserProfile # ИЗМЕНЕНО: добавляем 'social_accounts' в список полей fields = ['id', 'user', 'role', 'balance', 'avatar_url', 'social_accounts'] def get_social_accounts(self, obj): # Эта функция получает все привязанные аккаунты для пользователя # и возвращает список их названий (например, ['steam', 'telegram']) social_auths = UserSocialAuth.objects.filter(user=obj.user) return [auth.provider for auth in social_auths] class NewsSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) class Meta: model = News fields = '__all__' class ForumCategorySerializer(serializers.ModelSerializer): class Meta: model = ForumCategory fields = '__all__' class ForumTopicSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) category = ForumCategorySerializer(read_only=True) class Meta: model = ForumTopic fields = '__all__' class ForumPostSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) topic = ForumTopicSerializer(read_only=True) class Meta: model = ForumPost fields = '__all__' class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = '__all__' class OrderSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) product = ProductSerializer(read_only=True) class Meta: model = Order fields = '__all__' class PromoCodeSerializer(serializers.ModelSerializer): class Meta: model = PromoCode fields = '__all__' class UserPromoCodeSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) promo_code = PromoCodeSerializer(read_only=True) class Meta: model = UserPromoCode fields = '__all__' class TicketSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) assigned_to = UserSerializer(read_only=True) class Meta: model = Ticket fields = '__all__' class TicketMessageSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) ticket = TicketSerializer(read_only=True) class Meta: model = TicketMessage fields = '__all__' class UserAttendanceSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) class Meta: model = UserAttendance fields = '__all__' class UserOnlineStatusSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) class Meta: model = UserOnlineStatus fields = '__all__' class ChatMessageSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) class Meta: model = ChatMessage fields = '__all__' ==================== FILE: game_servers\templates\auth_complete.html ==================== Authentication Complete

Authentication successful. Please wait...

==================== FILE: game_servers\templates\auth_failed.html ==================== Authentication Failed

Authentication failed. You can close this window.

==================== FILE: game_servers\tests.py ==================== from django.test import TestCase # Create your tests here. ==================== FILE: game_servers\urls.py ==================== from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import ( GameViewSet, ServerViewSet, NewsViewSet, ForumCategoryViewSet, ForumTopicViewSet, ForumPostViewSet, ProductViewSet, OrderViewSet, PromoCodeViewSet, TicketViewSet, TicketMessageViewSet ) # Этот файл теперь отвечает только за маршрутизацию ViewSet'ов для приложения game_servers. # Главный файл urls.py (breakout_zone/urls.py) подключит эти маршруты с префиксом /api/. router = DefaultRouter() router.register(r'games', GameViewSet) router.register(r'servers', ServerViewSet) router.register(r'news', NewsViewSet) router.register(r'forum-categories', ForumCategoryViewSet) router.register(r'forum-topics', ForumTopicViewSet) router.register(r'forum-posts', ForumPostViewSet) router.register(r'products', ProductViewSet) router.register(r'orders', OrderViewSet) router.register(r'promo-codes', PromoCodeViewSet) router.register(r'tickets', TicketViewSet) router.register(r'ticket-messages', TicketMessageViewSet) # Больше не нужно определять здесь admin, auth или function-based views, # так как они централизованы в breakout_zone/urls.py. urlpatterns = [ path('', include(router.urls)), ] ==================== FILE: game_servers\views.py ==================== import time import socket import requests from django.shortcuts import render from rest_framework import viewsets, status from rest_framework.response import Response from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated, AllowAny from django.contrib.auth import authenticate from django.shortcuts import redirect from django.conf import settings from rest_framework.authtoken.models import Token from django.contrib.auth.models import User from django.http import JsonResponse from django.contrib.auth import login from django.contrib.auth.decorators import login_required from .models import ( Game, Server, Product, Category, UserProfile, News, ForumCategory, ForumTopic, ForumPost, UserAttendance, UserOnlineStatus, ChatMessage, Ticket, TicketMessage, PromoCode, UserPromoCode, Order ) from .serializers import ( GameSerializer, ServerSerializer, UserProfileSerializer, NewsSerializer, ForumCategorySerializer, ForumTopicSerializer, ForumPostSerializer, ProductSerializer, OrderSerializer, PromoCodeSerializer, UserPromoCodeSerializer, TicketSerializer, TicketMessageSerializer, UserAttendanceSerializer, UserOnlineStatusSerializer, ChatMessageSerializer ) try: import a2s A2S_AVAILABLE = True except ImportError: A2S_AVAILABLE = False def social_error_view(request): profile_url = '/#/profile' error_url = f"{profile_url}?error=already_associated" return redirect(error_url) def find_query_port_via_steam_api(ip, game_port): """Использует Steam Web API для поиска правильного порта запроса.""" try: url = f"http://api.steampowered.com/ISteamApps/GetServersAtAddress/v0001/?addr={ip}" response = requests.get(url, timeout=5) response.raise_for_status() data = response.json() if data.get('response', {}).get('servers'): for server in data['response']['servers']: if server.get('gameport') == game_port: if 'addr' in server: try: _, q_port_str = server['addr'].split(':') return int(q_port_str) except (ValueError, IndexError): continue except requests.RequestException: pass return None def probe_query_port(ip, base_port, max_range=20): """Проверяет порты для поиска рабочего порта запроса.""" ports_to_try = {base_port + 1, base_port + 2, 27015, 27016, 27017, base_port + 24714} for offset in range(1, max_range + 1): ports_to_try.add(base_port + offset) for port in sorted(list(ports_to_try)): try: address = (ip, port) info = a2s.info(address, timeout=1.5) return port, info except (socket.timeout, OSError): continue return None, None def get_server_info_a2s(ip, port): """Общая функция для получения информации через a2s.""" start_time = time.time() info = a2s.info((ip, port), timeout=4.0) ping = int((time.time() - start_time) * 1000) return True, ping, info.player_count, info.max_players def check_dayz_server(ip, port): """Проверяет сервер DayZ с несколькими стратегиями.""" query_port = find_query_port_via_steam_api(ip, port) if query_port: try: return get_server_info_a2s(ip, query_port) except (socket.timeout, OSError): pass # Пробуем другие методы если API-порт не отвечает found_port, info = probe_query_port(ip, port) if found_port and info: ping = int((time.time() - info.ping) * 1000) if hasattr(info, 'ping') else 0 return True, ping, info.player_count, info.max_players return False, 0, 0, 0 def check_zomboid_server(ip, port): """Проверяет сервер Project Zomboid.""" query_port = find_query_port_via_steam_api(ip, port) or (port + 1) try: return get_server_info_a2s(ip, query_port) except (socket.timeout, OSError): return False, 0, 0, 0 def check_source_server(ip, port): """Проверяет сервер на движке Source (CS2).""" try: return get_server_info_a2s(ip, port) except (socket.timeout, OSError): return False, 0, 0, 0 def get_real_server_status(ip, port, game_id): """Основная функция-диспетчер для реальной проверки статуса.""" if not A2S_AVAILABLE: return False, 0, 0, 0 # Возвращаем оффлайн, если библиотека не установлена if game_id == 1: # DayZ return check_dayz_server(ip, port) elif game_id == 2: # Project Zomboid return check_zomboid_server(ip, port) elif game_id == 3: # CS2 return check_source_server(ip, port) else: return False, 0, 0, 0 # --- Конец реальной логики проверки серверов --- @api_view(['GET']) def game_data(request): games = Game.objects.all() servers = Server.objects.all() game_serializer = GameSerializer(games, many=True) server_serializer = ServerSerializer(servers, many=True) return Response({ 'games': game_serializer.data, 'servers': server_serializer.data }) @api_view(['POST']) def update_server_status(request): """ Обновляет статус всех серверов, используя реальные данные, а не симуляцию. """ servers = Server.objects.all() for server in servers: try: ip, port_str = server.ip_address.split(':') port = int(port_str) except ValueError: continue is_online, ping, player_count, max_players = get_real_server_status(ip, port, server.game.id) if is_online: server.current_players = player_count server.ping = ping if max_players is not None and max_players > 0: server.max_players = max_players server.save() else: server.current_players = 0 server.ping = 0 server.save() # Возвращаем обновленные данные всех игр и серверов all_games = Game.objects.all() all_servers = Server.objects.all() game_serializer = GameSerializer(all_games, many=True) server_serializer = ServerSerializer(all_servers, many=True) return Response({ 'games': game_serializer.data, 'servers': server_serializer.data }) # --- Остальные API views без изменений --- # Authentication views @api_view(['POST']) @permission_classes([AllowAny]) def register_user(request): username = request.data.get('username') email = request.data.get('email') password = request.data.get('password') if not username or not email or not password: return Response({'error': 'Username, email, and password are required'}, status=status.HTTP_400_BAD_REQUEST) if User.objects.filter(username=username).exists(): return Response({'error': 'Username already exists'}, status=status.HTTP_400_BAD_REQUEST) if User.objects.filter(email=email).exists(): return Response({'error': 'Email already exists'}, status=status.HTTP_400_BAD_REQUEST) try: user = User.objects.create_user( username=username, email=email, password=password ) UserProfile.objects.create(user=user, role='user', balance=0.00) token, created = Token.objects.get_or_create(user=user) return Response({ 'token': token.key, 'user': { 'id': user.id, 'username': user.username, 'email': user.email } }, status=status.HTTP_201_CREATED) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @permission_classes([AllowAny]) def login_user(request): username = request.data.get('username') password = request.data.get('password') if not username or not password: return Response({'error': 'Username and password are required'}, status=status.HTTP_400_BAD_REQUEST) user = authenticate(username=username, password=password) if not user: return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) token, created = Token.objects.get_or_create(user=user) online_status, created = UserOnlineStatus.objects.get_or_create(user=user) online_status.is_online = True online_status.save() return Response({ 'token': token.key, 'user': { 'id': user.id, 'username': user.username, 'email': user.email } }) @api_view(['POST']) @permission_classes([IsAuthenticated]) def logout_user(request): try: online_status, created = UserOnlineStatus.objects.get_or_create(user=request.user) online_status.is_online = False online_status.save() request.user.auth_token.delete() return Response({'message': 'Successfully logged out'}, status=status.HTTP_200_OK) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET']) @permission_classes([IsAuthenticated]) def user_profile(request): try: profile = UserProfile.objects.get(user=request.user) except UserProfile.DoesNotExist: profile = UserProfile.objects.create(user=request.user, role='user', balance=0.00) serializer = UserProfileSerializer(profile) return Response(serializer.data) @api_view(['GET']) def news_list(request): news = News.objects.filter(is_published=True).order_by('-created_at') serializer = NewsSerializer(news, many=True) return Response(serializer.data) @api_view(['GET']) def forum_categories(request): categories = ForumCategory.objects.all().order_by('order') serializer = ForumCategorySerializer(categories, many=True) return Response(serializer.data) @api_view(['GET']) def forum_topics(request, category_id): topics = ForumTopic.objects.filter(category_id=category_id).order_by('-is_pinned', '-updated_at') serializer = ForumTopicSerializer(topics, many=True) return Response(serializer.data) @api_view(['GET']) def forum_posts(request, topic_id): posts = ForumPost.objects.filter(topic_id=topic_id).order_by('created_at') serializer = ForumPostSerializer(posts, many=True) return Response(serializer.data) @api_view(['GET']) def products_by_server(request, server_id): try: server = Server.objects.get(id=server_id) except Server.DoesNotExist: return Response({'error': 'Server not found'}, status=status.HTTP_404_NOT_FOUND) products = Product.objects.filter(game=server.game, is_active=True) serializer = ProductSerializer(products, many=True) return Response(serializer.data) @api_view(['GET']) def products_by_category(request, category_name): try: products = Product.objects.filter( category=category_name, is_active=True ).select_related('game') products_data = [] for product in products: products_data.append({ 'id': product.id, 'name': product.name, 'description': product.description, 'price': str(product.price), 'image_url': product.image_url or '', 'is_active': product.is_active, 'category': product.category, 'game': { 'id': product.game.id, 'title': product.game.title, 'genre': product.game.genre, 'description': product.game.description, 'coverImageUrl': product.game.cover_image_url, 'status': product.game.status, 'generalDonationLink': product.game.general_donation_link, } }) return JsonResponse({'products': products_data}) except Exception as e: return JsonResponse({'error': str(e)}, status=500) @api_view(['GET']) def products_by_game(request, game_id): products = Product.objects.filter(game_id=game_id, is_active=True) serializer = ProductSerializer(products, many=True) return Response(serializer.data) @api_view(['POST']) @permission_classes([IsAuthenticated]) def apply_promo_code(request): code = request.data.get('code') if not code: return Response({'error': 'Promo code is required'}, status=status.HTTP_400_BAD_REQUEST) try: promo_code = PromoCode.objects.get(code=code, is_active=True) except PromoCode.DoesNotExist: return Response({'error': 'Invalid promo code'}, status=status.HTTP_400_BAD_REQUEST) from django.utils import timezone if promo_code.valid_from > timezone.now() or promo_code.valid_to < timezone.now(): return Response({'error': 'Promo code has expired'}, status=status.HTTP_400_BAD_REQUEST) if promo_code.times_used >= promo_code.max_uses: return Response({'error': 'Promo code has reached maximum uses'}, status=status.HTTP_400_BAD_REQUEST) if UserPromoCode.objects.filter(user=request.user, promo_code=promo_code).exists(): return Response({'error': 'You have already used this promo code'}, status=status.HTTP_400_BAD_REQUEST) user_profile = UserProfile.objects.get(user=request.user) if promo_code.balance_amount: user_profile.balance += promo_code.balance_amount user_profile.save() UserPromoCode.objects.create(user=request.user, promo_code=promo_code) promo_code.times_used += 1 promo_code.save() return Response({ 'success': 'Promo code applied successfully', 'balance_added': str(promo_code.balance_amount) if promo_code.balance_amount else '0.00', 'new_balance': str(user_profile.balance) }) @api_view(['POST']) @permission_classes([IsAuthenticated]) def create_ticket(request): subject = request.data.get('subject') content = request.data.get('content') priority = request.data.get('priority', 'medium') if not subject or not content: return Response({'error': 'Subject and content are required'}, status=status.HTTP_400_BAD_REQUEST) ticket = Ticket.objects.create( user=request.user, subject=subject, content=content, priority=priority ) serializer = TicketSerializer(ticket) return Response(serializer.data, status=status.HTTP_201_CREATED) @api_view(['POST']) @permission_classes([IsAuthenticated]) def update_user_status(request): from django.utils import timezone from datetime import timedelta online_status, created = UserOnlineStatus.objects.get_or_create(user=request.user) online_status.is_online = True online_status.current_page = request.data.get('current_page', '') online_status.save() online_status.last_activity = timezone.now() online_status.save() today = timezone.now().date() attendance, created = UserAttendance.objects.get_or_create( user=request.user, date=today, defaults={'login_count': 1} ) if not created: attendance.login_count += 1 attendance.save() return Response({ 'status': 'success', 'online_status': UserOnlineStatusSerializer(online_status).data, 'attendance': UserAttendanceSerializer(attendance).data }) @api_view(['GET']) def get_online_users(request): from django.utils import timezone from datetime import timedelta five_minutes_ago = timezone.now() - timedelta(minutes=5) online_users = UserOnlineStatus.objects.filter( is_online=True, last_activity__gte=five_minutes_ago ).select_related('user') serializer = UserOnlineStatusSerializer(online_users, many=True) return Response(serializer.data) @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_chat_messages(request): messages = ChatMessage.objects.select_related('author').order_by('-created_at')[:50] messages = reversed(list(messages)) serializer = ChatMessageSerializer(messages, many=True) return Response(serializer.data) @api_view(['POST']) @permission_classes([IsAuthenticated]) def send_chat_message(request): content = request.data.get('content', '').strip() if not content: return Response({'error': 'Message content is required'}, status=status.HTTP_400_BAD_REQUEST) if len(content) > 500: return Response({'error': 'Message too long (max 500 characters)'}, status=status.HTTP_400_BAD_REQUEST) message = ChatMessage.objects.create( author=request.user, content=content ) serializer = ChatMessageSerializer(message) return Response(serializer.data, status=status.HTTP_201_CREATED) class GameViewSet(viewsets.ModelViewSet): queryset = Game.objects.all() serializer_class = GameSerializer class ServerViewSet(viewsets.ModelViewSet): queryset = Server.objects.all() serializer_class = ServerSerializer class NewsViewSet(viewsets.ModelViewSet): queryset = News.objects.all() serializer_class = NewsSerializer class ForumCategoryViewSet(viewsets.ModelViewSet): queryset = ForumCategory.objects.all() serializer_class = ForumCategorySerializer class ForumTopicViewSet(viewsets.ModelViewSet): queryset = ForumTopic.objects.all() serializer_class = ForumTopicSerializer class ForumPostViewSet(viewsets.ModelViewSet): queryset = ForumPost.objects.all() serializer_class = ForumPostSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer class OrderViewSet(viewsets.ModelViewSet): queryset = Order.objects.all() serializer_class = OrderSerializer class PromoCodeViewSet(viewsets.ModelViewSet): queryset = PromoCode.objects.all() serializer_class = PromoCodeSerializer class TicketViewSet(viewsets.ModelViewSet): queryset = Ticket.objects.all() serializer_class = TicketSerializer class TicketMessageViewSet(viewsets.ModelViewSet): queryset = TicketMessage.objects.all() serializer_class = TicketMessageSerializer def custom_social_auth_complete(request, backend): user = request.user if not user or not user.is_authenticated: return redirect('http://localhost:3000/?auth=failed') # создаём токен token, _ = Token.objects.get_or_create(user=user) # логиним пользователя login(request, user) # отправляем токен на фронтенд redirect_url = f"http://localhost:3000/?token={token.key}&user={user.username}" return redirect(redirect_url) def categories_list(request): """Get all categories""" try: categories = Product.objects.filter(is_active=True).values_list('category', flat=True).distinct() categories = [cat for cat in categories if cat] return JsonResponse({'categories': list(categories)}) except Exception as e: return JsonResponse({'error': str(e)}, status=500) @login_required def auth_complete_token_sender(request): """ Это представление вызывается после успешной социальной аутентификации. Оно генерирует токен и перенаправляет пользователя на фронтенд, передавая токен в хэше URL. """ user = request.user token, _ = Token.objects.get_or_create(user=user) # --- ИСПРАВЛЕННЫЙ БЛОК --- # Явно получаем URL фронтенда из настроек Django. frontend_url = settings.FRONTEND_URL # Убираем возможный слеш в конце URL и добавляем его снова, чтобы избежать двойных слешей. # Затем добавляем хэш с токеном, который ожидает фронтенд в App.tsx. redirect_url = f"{frontend_url.rstrip('/')}/#token={token.key}" # Выполняем переадресацию. Браузер во всплывающем окне перейдет на адрес фронтенда. return redirect(redirect_url) ==================== FILE: games\__init__.py ==================== ==================== FILE: games\admin.py ==================== from django.contrib import admin ==================== FILE: games\apps.py ==================== from django.apps import AppConfig class GamesConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'games' ==================== FILE: games\migrations\__init__.py ==================== ==================== FILE: games\models.py ==================== from django.db import models ==================== FILE: games\tests.py ==================== from django.test import TestCase # Create your tests here. ==================== FILE: games\views.py ==================== from django.shortcuts import render # Create your views here. ==================== FILE: manage.py ==================== import os import sys def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'breakout_zone.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == '__main__': main() ==================== FILE: test_frontend_connection.py ==================== import requests import json def test_connection(): try: backend_response = requests.get('http://localhost:8000/api/data/') print(f"Backend API Status: {backend_response.status_code}") if backend_response.status_code == 200: data = backend_response.json() print(f"Games count: {len(data['games'])}") print(f"Servers count: {len(data['servers'])}") print("Backend API is working correctly!") else: print("Backend API returned an error") return False cors_response = requests.get('http://localhost:8000/api/data/', headers={'Origin': 'http://localhost:3001'}) print(f"CORS Test Status: {cors_response.status_code}") if cors_response.status_code == 200: print("CORS is configured correctly!") else: print("CORS configuration issue") return False return True except Exception as e: print(f"Error testing connection: {e}") return False if __name__ == "__main__": print("Testing frontend-backend connection...") if test_connection(): print("\n✅ All tests passed! The frontend should be able to fetch data from the backend.") else: print("\n❌ Tests failed. Please check the configuration.") ==================== FILE: update_game_statuses.py ==================== import os import sys import django # Add the project directory to the Python path sys.path.append(os.path.dirname(os.path.abspath(__file__))) # Set the Django settings module os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'breakout_zone.settings') # Setup Django django.setup() from game_servers.models import Game def update_game_statuses(): # Set all games to 'soon' first Game.objects.all().update(status='soon') # Set DayZ to 'live' try: dayz_game = Game.objects.get(title='DayZ') dayz_game.status = 'live' dayz_game.save() print("Successfully updated DayZ status to live") except Game.DoesNotExist: print("DayZ game not found in database") # Report results games = Game.objects.all() print("\nCurrent game statuses:") for game in games: print(f"{game.title}: {game.status}") if __name__ == "__main__": update_game_statuses()