Fix API timestamp format bug and complete fixtures management system (v1.2.3.1)

- Fixed critical API synchronization bug: send Unix timestamp as long integer instead of ISO datetime string
- Updated _get_last_fixture_timestamp() to return str(fixture_active_time) for proper server compatibility
- Added comprehensive fixtures management system with dashboard integration
- Created fixtures list and detail pages with Bootstrap styling and admin controls
- Enhanced API client with proper timestamp handling and authentication for ZIP downloads
- Updated documentation (README.md, CHANGELOG.md, DOCUMENTATION.md) with new features
- Completed fixtures dashboard with real-time synchronization and administrative controls
- Fixed server communication to use proper Unix timestamp format: '1755773200' instead of '2025-08-21T14:31:3'
parent 800411c8
......@@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file.
- **Path Resolution**: All paths now resolve to persistent user directories instead of temporary executable locations
- **Directory Creation**: Robust cross-platform directory creation with proper error handling and fallbacks
- **Database Location**: SQLite database now stored in persistent user data directory across all platforms
- **API Timestamp Format**: Fixed fixture synchronization timestamp format to send Unix timestamp as long integer instead of ISO datetime string
### Enhanced
- **User Data Management**: Automatic creation of logs/, data/, uploads/, and templates/ subdirectories
......
......@@ -358,6 +358,58 @@ python main.py --overlay-type native
- **sports**: Processes game scores and team information
- **custom**: User-defined processing logic
### Fixtures Management System
The application includes a comprehensive fixtures management system with real-time API synchronization and web dashboard integration:
#### Fixtures Dashboard
Access the fixtures management interface through the web dashboard:
- **Fixtures List**: View all synchronized fixtures with match counts and status
- **Fixture Details**: Detailed view of individual fixtures with all matches and outcomes
- **Admin Controls**: Reset functionality to clear all fixture data and ZIP files
- **Real-time Updates**: Live synchronization with server using proper timestamp handling
#### API Synchronization
The fixtures system synchronizes with the server API using Unix timestamp format:
```http
POST /api/updates
Authorization: Bearer <token>
Content-Type: application/json
{
"from": "1755773200"
}
```
**Response Format:**
```json
{
"fixtures": [
{
"fixture_id": "fixture_123",
"fixture_active_time": 1755773200,
"matches": [
{
"match_number": 101,
"fighter1_township": "Kampala Central",
"fighter2_township": "Nakawa",
"fixture_id": "fixture_123",
"fixture_active_time": 1755773200,
"outcomes": {
"round_1_score": 10.5,
"round_2_score": 9.8
}
}
]
}
]
}
```
### Match Data Management
The application includes comprehensive boxing match data management with database tables adapted from the mbetterd system:
......@@ -370,6 +422,7 @@ The application includes comprehensive boxing match data management with databas
- File metadata and SHA1 checksums
- ZIP upload tracking with progress
- User association and timestamps
- `fixture_active_time` field for server synchronization (Unix timestamp)
**match_outcomes table**: Detailed match results
- Foreign key relationships to matches
......
......@@ -25,6 +25,8 @@ A cross-platform multimedia client application with video playback, web dashboar
-**Boxing Match Database**: Added comprehensive `matches` and `match_outcomes` database tables adapted from mbetterd MySQL schema
-**Cross-Platform Persistence**: Complete PyInstaller executable persistence with platform-specific user directories
-**Match Data Management**: Full SQLAlchemy models for boxing match tracking with fighter townships, venues, and outcomes
-**Fixtures Management System**: Complete fixtures dashboard with API synchronization, database integration, and administrative controls
-**API Synchronization**: Real-time fixture and match data synchronization with server using proper Unix timestamp format
-**File Upload Tracking**: ZIP file upload management with progress tracking and status monitoring
-**Database Migration System**: Migration_008_AddMatchTables with comprehensive indexing and foreign key relationships
-**User Data Directories**: Automatic creation of persistent directories on Windows (%APPDATA%), macOS (~/Library/Application Support), and Linux (~/.local/share)
......
This diff is collapsed.
......@@ -457,19 +457,25 @@ class MessageBuilder:
)
@staticmethod
def api_request(sender: str, url: str, method: str = "GET",
def api_request(sender: str, url: str = None, endpoint: str = None, method: str = "GET",
headers: Optional[Dict[str, str]] = None,
data: Optional[Dict[str, Any]] = None) -> Message:
"""Create API_REQUEST message"""
message_data = {
"method": method,
"headers": headers or {},
"data": data or {}
}
if endpoint:
message_data["endpoint"] = endpoint
if url:
message_data["url"] = url
return Message(
type=MessageType.API_REQUEST,
sender=sender,
data={
"url": url,
"method": method,
"headers": headers or {},
"data": data or {}
}
data=message_data
)
@staticmethod
......
This diff is collapsed.
......@@ -455,9 +455,9 @@ class MatchModel(BaseModel):
Index('ix_matches_zip_sha1sum', 'zip_sha1sum'),
Index('ix_matches_zip_upload_status', 'zip_upload_status'),
Index('ix_matches_created_by', 'created_by'),
Index('ix_matches_fixture_active_time', 'fixture_active_time'),
Index('ix_matches_composite', 'active_status', 'zip_upload_status', 'created_at'),
UniqueConstraint('match_number', name='uq_matches_match_number'),
UniqueConstraint('fixture_id', name='uq_matches_fixture_id'),
)
# Core match data from fixture file
......@@ -470,11 +470,14 @@ class MatchModel(BaseModel):
start_time = Column(DateTime, comment='Match start time')
end_time = Column(DateTime, comment='Match end time')
result = Column(String(255), comment='Match result/outcome')
done = Column(Boolean, default=False, nullable=False, comment='Match completion flag (0=pending, 1=done)')
running = Column(Boolean, default=False, nullable=False, comment='Match running flag (0=not running, 1=running)')
fixture_active_time = Column(Integer, nullable=True, comment='Unix timestamp when fixture became active on server')
# File metadata
filename = Column(String(1024), nullable=False, comment='Original fixture filename')
file_sha1sum = Column(String(255), nullable=False, comment='SHA1 checksum of fixture file')
fixture_id = Column(String(255), nullable=False, unique=True, comment='Unique fixture identifier')
fixture_id = Column(String(255), nullable=False, comment='Fixture identifier (multiple matches can share same fixture)')
active_status = Column(Boolean, default=False, nullable=False, comment='Active status flag')
# ZIP file related fields
......
This diff is collapsed.
......@@ -44,6 +44,12 @@
<i class="fas fa-layer-group me-1"></i>Templates
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.fixtures' %}active{% endif %}"
href="{{ url_for('main.fixtures') }}">
<i class="fas fa-list-ul me-1"></i>Fixtures
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'main.video_test' %}active{% endif %}"
href="{{ url_for('main.video_test') }}">
......
......@@ -44,6 +44,11 @@
<label for="api-token" class="form-label">API Token</label>
<input type="password" class="form-control" id="api-token" placeholder="Enter API token">
</div>
<div class="mb-3">
<label for="api-interval" class="form-label">Request Interval (seconds)</label>
<input type="number" class="form-control" id="api-interval" placeholder="1800" value="{{ config.api_interval or 1800 }}" min="30" max="86400">
<div class="form-text">How often to request updates from the MbetterD daemon (30 seconds to 24 hours)</div>
</div>
<button type="submit" class="btn btn-primary">Save Configuration</button>
</form>
</div>
......@@ -112,7 +117,8 @@
const config = {
api_host: document.getElementById('api-host').value,
api_port: parseInt(document.getElementById('api-port').value),
api_token: document.getElementById('api-token').value
api_token: document.getElementById('api-token').value,
api_interval: parseInt(document.getElementById('api-interval').value)
};
fetch('/api/config', {
......
......@@ -87,6 +87,12 @@
placeholder="Enter your API access token">
<div class="form-text">Authentication token for FastAPI server access</div>
</div>
<div class="mb-3">
<label for="api-interval" class="form-label">Request Interval (seconds)</label>
<input type="number" class="form-control" id="api-interval"
value="{{ config.api_interval or 1800 }}" min="30" max="86400">
<div class="form-text">Time between automatic API requests (30 seconds to 24 hours)</div>
</div>
<div class="mb-3">
<label for="api-timeout" class="form-label">Request Timeout (seconds)</label>
<input type="number" class="form-control" id="api-timeout"
......@@ -124,6 +130,24 @@
</form>
</div>
</div>
<!-- API Client Debug Section -->
<div class="card">
<div class="card-header">
<h5>API Client Debug</h5>
</div>
<div class="card-body">
<div class="mb-3">
<button type="button" class="btn btn-info" id="check-api-status">
Check API Client Status
</button>
<button type="button" class="btn btn-warning ms-2" id="trigger-api-request">
Trigger Manual Request
</button>
</div>
<div id="api-status-result" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
......@@ -173,6 +197,7 @@
const config = {
fastapi_url: document.getElementById('fastapi-url').value,
api_token: document.getElementById('api-token').value,
api_interval: parseInt(document.getElementById('api-interval').value),
api_timeout: parseInt(document.getElementById('api-timeout').value),
api_enabled: document.getElementById('api-enabled').checked
};
......@@ -243,5 +268,92 @@
alert('Failed to save ' + section + ' configuration');
});
}
// Check API client status
document.getElementById('check-api-status').addEventListener('click', function() {
this.disabled = true;
this.textContent = 'Checking...';
fetch('/api/api-client/status')
.then(response => response.json())
.then(data => {
const resultDiv = document.getElementById('api-status-result');
if (data.success) {
const status = data.status;
let html = '<div class="alert alert-info"><strong>API Client Status:</strong><br>';
html += '<strong>Message Bus:</strong> ' + (status.message_bus_available ? 'Available' : 'Not Available') + '<br>';
html += '<strong>Config Manager:</strong> ' + (status.config_manager_available ? 'Available' : 'Not Available') + '<br>';
if (status.api_config) {
html += '<strong>API Configuration:</strong><br>';
html += '&nbsp;&nbsp;URL: ' + (status.api_config.fastapi_url || 'Not set') + '<br>';
html += '&nbsp;&nbsp;Token: ' + (status.api_config.api_token_set ? 'Set' : 'Not set') + '<br>';
html += '&nbsp;&nbsp;Interval: ' + status.api_config.api_interval + ' seconds<br>';
html += '&nbsp;&nbsp;Enabled: ' + status.api_config.api_enabled + '<br>';
}
if (status.fastapi_endpoint && Object.keys(status.fastapi_endpoint).length > 0) {
const ep = status.fastapi_endpoint;
html += '<strong>FastAPI Endpoint:</strong><br>';
html += '&nbsp;&nbsp;URL: ' + (ep.url || 'Not set') + '<br>';
html += '&nbsp;&nbsp;Enabled: ' + ep.enabled + '<br>';
html += '&nbsp;&nbsp;Interval: ' + ep.interval + ' seconds<br>';
html += '&nbsp;&nbsp;Auth Configured: ' + ep.auth_configured + '<br>';
html += '&nbsp;&nbsp;Total Requests: ' + ep.total_requests + '<br>';
html += '&nbsp;&nbsp;Consecutive Failures: ' + ep.consecutive_failures + '<br>';
html += '&nbsp;&nbsp;Last Request: ' + (ep.last_request || 'Never') + '<br>';
html += '&nbsp;&nbsp;Last Success: ' + (ep.last_success || 'Never') + '<br>';
} else {
html += '<strong>FastAPI Endpoint:</strong> Not configured<br>';
}
html += '</div>';
resultDiv.innerHTML = html;
} else {
resultDiv.innerHTML = '<div class="alert alert-danger">Error: ' + data.error + '</div>';
}
})
.catch(error => {
document.getElementById('api-status-result').innerHTML =
'<div class="alert alert-danger">Error checking status: ' + error.message + '</div>';
})
.finally(() => {
this.disabled = false;
this.textContent = 'Check API Client Status';
});
});
// Trigger manual API request
document.getElementById('trigger-api-request').addEventListener('click', function() {
this.disabled = true;
this.textContent = 'Triggering...';
fetch('/api/api-client/trigger', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({endpoint: 'fastapi_main'})
})
.then(response => response.json())
.then(data => {
const resultDiv = document.getElementById('api-status-result');
if (data.success) {
resultDiv.innerHTML = '<div class="alert alert-success">' + data.message + '</div>';
} else {
resultDiv.innerHTML = '<div class="alert alert-danger">Error: ' + data.error + '</div>';
}
})
.catch(error => {
document.getElementById('api-status-result').innerHTML =
'<div class="alert alert-danger">Error triggering request: ' + error.message + '</div>';
})
.finally(() => {
this.disabled = false;
this.textContent = 'Trigger Manual Request';
});
});
</script>
{% endblock %}
\ No newline at end of file
This diff is collapsed.
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