Update new bet pages to manage cross data

parent 4e7a7dde
...@@ -980,6 +980,7 @@ class APIClient(ThreadedComponent): ...@@ -980,6 +980,7 @@ class APIClient(ThreadedComponent):
# Subscribe to messages # Subscribe to messages
self.message_bus.subscribe(self.name, MessageType.CONFIG_UPDATE, self._handle_config_update) self.message_bus.subscribe(self.name, MessageType.CONFIG_UPDATE, self._handle_config_update)
self.message_bus.subscribe(self.name, MessageType.API_REQUEST, self._handle_api_request) self.message_bus.subscribe(self.name, MessageType.API_REQUEST, self._handle_api_request)
self.message_bus.subscribe(self.name, MessageType.FORCE_API_UPDATE, self._handle_force_api_update)
# Validate existing fixtures on startup # Validate existing fixtures on startup
updates_handler = self.response_handlers.get('updates') updates_handler = self.response_handlers.get('updates')
...@@ -1256,13 +1257,21 @@ class APIClient(ThreadedComponent): ...@@ -1256,13 +1257,21 @@ class APIClient(ThreadedComponent):
# For FastAPI /api/updates endpoint, add 'from' parameter and rustdesk_id if provided # For FastAPI /api/updates endpoint, add 'from' parameter and rustdesk_id if provided
if endpoint.name == 'fastapi_main' and 'updates' in endpoint.url.lower(): if endpoint.name == 'fastapi_main' and 'updates' in endpoint.url.lower():
last_timestamp = self._get_last_fixture_timestamp() # Check if forced timestamp is requested (for empty templates table scenario)
if last_timestamp: force_timestamp = getattr(endpoint, 'force_timestamp', False)
request_data['from'] = last_timestamp
logger.debug("Adding 'from' parameter to {} request: {}".format(endpoint.name, last_timestamp)) if force_timestamp:
# Force timestamp to 0 to download latest fixture from server
request_data['from'] = '0'
logger.debug("Forcing timestamp to 0 to download latest fixture from server")
else: else:
# When no fixtures exist, send empty request to get all data last_timestamp = self._get_last_fixture_timestamp()
logger.debug("No fixtures found, sending empty request to {}".format(endpoint.name)) if last_timestamp:
request_data['from'] = last_timestamp
logger.debug("Adding 'from' parameter to {} request: {}".format(endpoint.name, last_timestamp))
else:
# When no fixtures exist, send empty request to get all data
logger.debug("No fixtures found, sending empty request to {}".format(endpoint.name))
# Add rustdesk_id to the updates call if provided # Add rustdesk_id to the updates call if provided
logger.debug("DEBUG: Checking rustdesk_id - value: '{}' (type: {}), bool: {}".format( logger.debug("DEBUG: Checking rustdesk_id - value: '{}' (type: {}), bool: {}".format(
...@@ -1528,6 +1537,8 @@ class APIClient(ThreadedComponent): ...@@ -1528,6 +1537,8 @@ class APIClient(ThreadedComponent):
self._handle_config_update(message) self._handle_config_update(message)
elif message.type == MessageType.API_REQUEST: elif message.type == MessageType.API_REQUEST:
self._handle_api_request(message) self._handle_api_request(message)
elif message.type == MessageType.FORCE_API_UPDATE:
self._handle_force_api_update(message)
# Add other message types as needed # Add other message types as needed
except Exception as e: except Exception as e:
logger.error(f"Failed to process message: {e}") logger.error(f"Failed to process message: {e}")
...@@ -1766,6 +1777,52 @@ class APIClient(ThreadedComponent): ...@@ -1766,6 +1777,52 @@ class APIClient(ThreadedComponent):
except Exception as e: except Exception as e:
logger.error(f"Failed to handle API request: {e}") logger.error(f"Failed to handle API request: {e}")
def _handle_force_api_update(self, message: Message):
"""Handle forced API update request from games thread"""
try:
logger.info("Received forced API update request: {}".format(message.data))
force_timestamp = message.data.get("force_timestamp", False)
reason = message.data.get("reason", "unknown")
logger.info("Forced API update triggered - reason: {}, force_timestamp: {}".format(reason, force_timestamp))
# Get fastapi_main endpoint
fastapi_endpoint = self.endpoints.get('fastapi_main')
if fastapi_endpoint:
# Reset last_request to trigger immediate execution
fastapi_endpoint.last_request = None
# Store force_timestamp flag for use in request
fastapi_endpoint.force_timestamp = force_timestamp
# Execute immediate request
logger.info("Executing immediate forced API update to synchronize match templates")
self._execute_endpoint_request(fastapi_endpoint)
# Clear the force_timestamp flag after execution
if hasattr(fastapi_endpoint, 'force_timestamp'):
delattr(fastapi_endpoint, 'force_timestamp')
logger.info("Forced API update completed successfully")
# Send response back to games thread
response_message = Message(
type=MessageType.FORCE_API_UPDATE,
sender=self.name,
recipient=message.sender,
data={
"status": "completed",
"reason": reason,
"timestamp": datetime.utcnow().isoformat()
}
)
self.message_bus.publish(response_message)
else:
logger.error("fastapi_main endpoint not found - cannot execute forced API update")
except Exception as e:
logger.error("Failed to handle forced API update: {}".format(e))
def get_endpoint_status(self, endpoint_name: str) -> Optional[Dict[str, Any]]: def get_endpoint_status(self, endpoint_name: str) -> Optional[Dict[str, Any]]:
"""Get status of a specific endpoint""" """Get status of a specific endpoint"""
endpoint = self.endpoints.get(endpoint_name) endpoint = self.endpoints.get(endpoint_name)
......
...@@ -349,6 +349,7 @@ class GamesThread(ThreadedComponent): ...@@ -349,6 +349,7 @@ class GamesThread(ThreadedComponent):
self.message_bus.subscribe(self.name, MessageType.MATCH_DONE, self._handle_match_done) self.message_bus.subscribe(self.name, MessageType.MATCH_DONE, self._handle_match_done)
self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status_request) self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status_request)
self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status) self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status)
self.message_bus.subscribe(self.name, MessageType.FORCE_API_UPDATE, self._handle_force_api_update)
# Send ready status # Send ready status
ready_message = MessageBuilder.system_status( ready_message = MessageBuilder.system_status(
...@@ -4313,6 +4314,12 @@ class GamesThread(ThreadedComponent): ...@@ -4313,6 +4314,12 @@ class GamesThread(ThreadedComponent):
if len(match_templates) < count: if len(match_templates) < count:
logger.warning(f"Only {len(match_templates)} validated match templates found, requested {count}") logger.warning(f"Only {len(match_templates)} validated match templates found, requested {count}")
# If templates table is completely empty, trigger forced API update
if len(match_templates) == 0:
logger.warning("Match templates table is empty - triggering forced API update to synchronize with server")
self._trigger_forced_api_update()
return match_templates return match_templates
# Select random templates # Select random templates
...@@ -4325,6 +4332,31 @@ class GamesThread(ThreadedComponent): ...@@ -4325,6 +4332,31 @@ class GamesThread(ThreadedComponent):
logger.error(f"Failed to select random match templates: {e}") logger.error(f"Failed to select random match templates: {e}")
return [] return []
def _trigger_forced_api_update(self):
"""Trigger forced API update to synchronize templates with server"""
try:
logger.info("Triggering forced API update to synchronize match templates")
force_update_message = Message(
type=MessageType.FORCE_API_UPDATE,
sender=self.name,
recipient="api_client",
data={
"force_timestamp": True, # Force timestamp to ensure latest fixture
"reason": "empty_templates_table"
}
)
self.message_bus.publish(force_update_message)
except Exception as e:
logger.error(f"Failed to trigger forced API update: {e}")
def _handle_force_api_update(self, message: Message):
"""Handle FORCE_API_UPDATE response from API client"""
try:
logger.info(f"Received FORCE_API_UPDATE response: {message.data}")
# Could trigger retry of START_GAME if needed
except Exception as e:
logger.error(f"Failed to handle FORCE_API_UPDATE response: {e}")
def _get_available_matches_excluding_recent(self, fixture_id: Optional[str], exclude_last_n: int, fighters_only: bool, session) -> List[MatchModel]: def _get_available_matches_excluding_recent(self, fixture_id: Optional[str], exclude_last_n: int, fighters_only: bool, session) -> List[MatchModel]:
"""Get available matches excluding the last N recent matches in the fixture""" """Get available matches excluding the last N recent matches in the fixture"""
try: try:
......
...@@ -75,6 +75,7 @@ class MessageType(Enum): ...@@ -75,6 +75,7 @@ class MessageType(Enum):
NEXT_MATCH = "NEXT_MATCH" NEXT_MATCH = "NEXT_MATCH"
GAME_STATUS = "GAME_STATUS" GAME_STATUS = "GAME_STATUS"
GAME_UPDATE = "GAME_UPDATE" GAME_UPDATE = "GAME_UPDATE"
FORCE_API_UPDATE = "FORCE_API_UPDATE"
# Custom messages (for future extensions) # Custom messages (for future extensions)
CUSTOM = "CUSTOM" CUSTOM = "CUSTOM"
......
...@@ -713,6 +713,14 @@ function updateAvailableMatchesDisplay(data, container) { ...@@ -713,6 +713,14 @@ function updateAvailableMatchesDisplay(data, container) {
// Add event listeners for amount inputs only // Add event listeners for amount inputs only
container.querySelectorAll('.amount-input').forEach(input => { container.querySelectorAll('.amount-input').forEach(input => {
input.addEventListener('input', function() { input.addEventListener('input', function() {
// Clear ALL other outcomes from ALL matches when entering an amount
// This ensures only ONE match and ONE outcome per bet
container.querySelectorAll('.amount-input').forEach(otherInput => {
if (otherInput !== this) {
otherInput.value = '';
}
});
updateBetSummary(); updateBetSummary();
}); });
}); });
......
...@@ -716,6 +716,14 @@ function updateAvailableMatchesDisplay(data, container) { ...@@ -716,6 +716,14 @@ function updateAvailableMatchesDisplay(data, container) {
// Add event listeners for amount inputs only // Add event listeners for amount inputs only
container.querySelectorAll('.amount-input').forEach(input => { container.querySelectorAll('.amount-input').forEach(input => {
input.addEventListener('input', function() { input.addEventListener('input', function() {
// Clear ALL other outcomes from ALL matches when entering an amount
// This ensures only ONE match and ONE outcome per bet
container.querySelectorAll('.amount-input').forEach(otherInput => {
if (otherInput !== this) {
otherInput.value = '';
}
});
updateBetSummary(); updateBetSummary();
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment