==================== FILE: __init__.py ==================== ==================== FILE: 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: apps.py ==================== from django.apps import AppConfig class GameServersConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'game_servers' ==================== FILE: 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: 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: 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: 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 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(self.style.SUCCESS( f"✓ SUCCESS: Server {server.name} is online - Players: {player_count}/{server.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: return False, 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}")) return port, info.player_count, 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.") return None, 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) return True, ping, info.player_count except (socket.timeout, OSError) as e: self.stdout.write(f" - Query port {query_port} from API failed: {str(e)}. Falling back to probing.") # If API fails or the returned port doesn't respond, probe for the query port found_port, player_count, ping = self.probe_query_port(ip, port) if found_port: return True, ping, player_count return False, 0, 0 def check_zomboid_server(self, ip, port): """ Check Project Zomboid server. (Original implementation was likely correct, no changes made here) """ try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(5.0) start_time = time.time() # Zomboid's query protocol is simple, just sending any data can work sock.sendto(b"\xfe\xfd\x00\x04\x05\x06\x07", (ip, port)) data, _ = sock.recvfrom(4096) ping = int((time.time() - start_time) * 1000) sock.close() # This parsing is complex and specific to Zomboid's response format # For now, let's assume a successful response means it's online # A more robust parser would be needed for player count return True, ping, 0 # Returning 0 players as parsing is non-trivial except Exception as e: self.stdout.write(self.style.ERROR( f" - Error checking Zomboid server {ip}:{port} - {str(e)}" )) return False, 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 return True, ping, player_count except Exception as e: self.stdout.write(self.style.ERROR( f" - Error checking CS2 server {ip}:{port} - {str(e)}" )) return False, 0, 0 ==================== FILE: 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: 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': 'soon', '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: 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': 'soon', '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:16261', '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: 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: 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: 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: 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: 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: 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: 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: migrations\0005_category.py ==================== # Generated by Django 5.2.7 on 2025-11-17 16:35 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('game_servers', '0004_product_category'), ] operations = [ # Empty migration - we're keeping the Product.category field as a CharField ] ==================== FILE: migrations\0006_category.py ==================== # Generated by Django 5.2.7 on 2025-11-17 16:40 import django.db.models.deletion from django.db import migrations, models def create_default_categories(apps, schema_editor): Category = apps.get_model('game_servers', 'Category') Game = apps.get_model('game_servers', 'Game') # Create default categories for DayZ game try: dayz_game = Game.objects.get(title='DayZ') except Game.DoesNotExist: return 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'} ] for cat_data in categories_data: Category.objects.get_or_create( name=cat_data['name'], game=dayz_game, defaults={'description': cat_data['description']} ) def reverse_migration(apps, schema_editor): # Reverse operation not needed pass class Migration(migrations.Migration): dependencies = [ ('game_servers', '0005_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')), ], ), migrations.RunPython(create_default_categories, reverse_migration), ] ==================== FILE: migrations\__init__.py ==================== ==================== FILE: 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: pipeline.py ==================== from django.contrib.auth.models import User from .models import UserProfile def save_profile(backend, user, response, *args, **kwargs): try: profile = UserProfile.objects.get(user=user) except UserProfile.DoesNotExist: profile = UserProfile.objects.create( user=user, role='user', balance=0.00 ) if backend.name == 'steam': pass elif backend.name == 'vk-oauth2': if hasattr(response, 'get'): if not profile.avatar_url: photo_url = response.get('photo_max_orig') or response.get('photo_200') if photo_url: profile.avatar_url = photo_url profile.save() return {'user': user} ==================== FILE: serializers.py ==================== from rest_framework import serializers 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 UserProfileSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) class Meta: model = UserProfile fields = '__all__' 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: tests.py ==================== from django.test import TestCase # Create your tests here. ==================== FILE: urls.py ==================== from django.urls import path from . import views urlpatterns = [ # This file can be used for app-specific URLs if needed ] ==================== FILE: views.py ==================== 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, login 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 .models import UserProfile, News, ForumCategory, ForumTopic, ForumPost, UserAttendance, UserOnlineStatus, ChatMessage, Ticket, TicketMessage, PromoCode, UserPromoCode, Order from .models import Game, Server, Product, Category from .serializers import GameSerializer, ServerSerializer, UserProfileSerializer, NewsSerializer, ForumCategorySerializer, ForumTopicSerializer, ForumPostSerializer, ProductSerializer, OrderSerializer, PromoCodeSerializer, UserPromoCodeSerializer, TicketSerializer, TicketMessageSerializer, UserAttendanceSerializer, UserOnlineStatusSerializer, ChatMessageSerializer @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 = server.ip_address.split(':') port = int(port) except ValueError: continue is_online, ping, player_count = check_server_status(ip, port, server.game.id) if is_online: server.current_players = player_count server.ping = ping server.save() else: server.current_players = 0 server.ping = 0 server.save() 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 }) def check_server_status(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 = get_simulated_player_count(game_id) return True, ping, player_count else: sock.close() return False, 0, 0 except Exception: return False, 0, 0 def get_simulated_player_count(game_id): 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) # 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): # Get the server try: server = Server.objects.get(id=server_id) except Server.DoesNotExist: return Response({'error': 'Server not found'}, status=status.HTTP_404_NOT_FOUND) # Get products for the server's game products = Product.objects.filter(game=server.game, is_active=True) # You could also add server-specific products here if you want # For now, we'll just return the game products serializer = ProductSerializer(products, many=True) return Response(serializer.data) @api_view(['GET']) def products_by_category(request, category_name): """Get products by category""" 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 user.is_authenticated: token, created = Token.objects.get_or_create(user=user) try: profile = UserProfile.objects.get(user=user) except UserProfile.DoesNotExist: profile = UserProfile.objects.create( user=user, role='user', balance=0.00 ) frontend_url = settings.LOGIN_REDIRECT_URL if not frontend_url.endswith('/'): frontend_url += '/' return redirect(f"{frontend_url}#token={token.key}") return redirect(settings.LOGIN_REDIRECT_URL) def categories_list(request): """Get all categories""" try: # Get unique categories from products categories = Product.objects.filter(is_active=True).values_list('category', flat=True).distinct() categories = [cat for cat in categories if cat] # Remove None/empty values return JsonResponse({'categories': list(categories)}) except Exception as e: return JsonResponse({'error': str(e)}, status=500)