Fix: Make client_id parameter optional in /api/reports/last-sync endpoint

- Make client_id query parameter optional in /api/reports/last-sync
- When client_id is not provided, return sync information for all clients associated with the authenticated user's API token
- Add requires_full_sync flag to each client's response when no records exist across ALL report tables
- Update REPORTS_SYNC_PROTOCOL_DOCUMENTATION.md with checking last sync status section
- Add GET LAST SYNC INFORMATION section to REPORTS_SYNC_API_SPECIFICATION.txt

This fix allows clients to query their sync status without needing to know their client_id, making the API more user-friendly and enabling automatic recovery detection.
parent ce5a588c
......@@ -384,6 +384,166 @@ retry_after (integer, optional)
- Seconds to wait before retrying
- Only present for rate limit errors
================================================================================
GET LAST SYNC INFORMATION
================================================================================
URL: /api/reports/last-sync
Method: GET
Authentication: Bearer Token (required)
Content-Type: application/json
Query Parameters:
----------------
client_id (string, optional)
- Unique identifier for the client
- If provided, retrieves sync information for a specific client
- If not provided, returns information for all clients associated with the authenticated user's API token
Request Example (Single Client):
--------------------------------
GET /api/reports/last-sync?client_id=abc123def456
Authorization: Bearer your-api-token-here
Request Example (All Clients):
------------------------------
GET /api/reports/last-sync
Authorization: Bearer your-api-token-here
Success Response for Single Client (200 OK):
--------------------------------------------
{
"success": true,
"client_id": "abc123def456",
"last_sync_id": "sync_20260201_082615_a1b2c3d4",
"last_sync_timestamp": "2026-02-01T08:26:15.123456",
"last_sync_type": "incremental",
"last_date_range": "all",
"last_start_date": "2026-01-01T00:00:00",
"last_end_date": "2026-02-01T08:26:15",
"total_syncs": 5,
"requires_full_sync": false,
"last_sync_summary": {
"total_payin": 15000.00,
"total_payout": 10500.00,
"net_profit": 4500.00,
"total_bets": 45,
"total_matches": 1,
"cap_compensation_balance": 5000.00
},
"server_timestamp": "2026-02-01T08:30:00.123456"
}
Success Response for All Clients (200 OK):
------------------------------------------
{
"success": true,
"message": "Found 2 client(s) for this user",
"clients": [
{
"client_id": "abc123def456",
"last_sync_id": "sync_20260201_082615_a1b2c3d4",
"last_sync_timestamp": "2026-02-01T08:26:15.123456",
"last_sync_type": "incremental",
"total_syncs": 5,
"requires_full_sync": false,
"last_sync_summary": {
"total_payin": 15000.00,
"total_payout": 10500.00,
"net_profit": 4500.00,
"total_bets": 45,
"total_matches": 1,
"cap_compensation_balance": 5000.00
}
},
{
"client_id": "xyz789ghi012",
"last_sync_id": null,
"last_sync_timestamp": null,
"last_sync_type": null,
"total_syncs": 0,
"requires_full_sync": true,
"last_sync_summary": null
}
],
"server_timestamp": "2026-02-01T08:30:00.123456"
}
Response Fields (Single Client):
--------------------------------
success (boolean, required)
- Always true for successful request
client_id (string, required)
- Client identifier for which sync information was retrieved
last_sync_id (string, optional)
- Sync ID of the most recent sync
- null if no sync records exist
last_sync_timestamp (string, optional)
- ISO 8601 format timestamp of the most recent sync
- null if no sync records exist
last_sync_type (string, optional)
- Type of the most recent sync ("full" or "incremental")
- null if no sync records exist
last_date_range (string, optional)
- Date range of the most recent sync
- null if no sync records exist
last_start_date (string, optional)
- Start date of the most recent sync
- null if no sync records exist
last_end_date (string, optional)
- End date of the most recent sync
- null if no sync records exist
total_syncs (integer, required)
- Total number of sync operations for this client
requires_full_sync (boolean, required)
- IMPORTANT: Indicates whether the server requires a full sync for this client
- Set to true when the server has NO records for this client across ALL report tables (bets, extraction_stats, match_reports)
- Client should perform a full sync (send all historical data) when this is true
- If ANY record exists for the client in ANY table, this flag will be false
last_sync_summary (object, optional)
- Summary statistics from the most recent sync
- null if no sync records exist
server_timestamp (string, required)
- ISO 8601 format timestamp on server
- When the server processed the request
Response Fields (All Clients):
-------------------------------
success (boolean, required)
- Always true for successful request
message (string, required)
- Human-readable message describing the result
clients (array, required)
- Array of client objects, each containing the same fields as the single client response
- Each client object includes: client_id, last_sync_id, last_sync_timestamp, last_sync_type, total_syncs, requires_full_sync, last_sync_summary
server_timestamp (string, required)
- ISO 8601 format timestamp on server
- When the server processed the request
Error Response (403 Forbidden):
--------------------------------
{
"success": false,
"error": "Access denied",
"details": "You do not have access to this client"
}
================================================================================
CLIENT RETRY BEHAVIOR
================================================================================
......
......@@ -265,6 +265,80 @@ The client should:
**Important**: The server checks across ALL report tables (bets, extraction_stats, match_reports) to determine if a full sync is required. If ANY record exists for the client in ANY table, `requires_full_sync` will be false.
### Checking Last Sync Status
The `/api/reports/last-sync` endpoint allows clients to check their sync status:
**Endpoint**: `GET /api/reports/last-sync?client_id=<client_id>` (optional)
**Authentication**: Bearer token required
**Parameters**:
- `client_id` (optional): Specific client ID to query. If not provided, returns information for all clients associated with the authenticated user's API token.
**Response when client_id is provided**:
```json
{
"success": true,
"client_id": "client_123",
"last_sync_id": "sync_20260201_214327_abc12345",
"last_sync_timestamp": "2026-02-01T21:43:27.249Z",
"last_sync_type": "incremental",
"total_syncs": 5,
"requires_full_sync": false,
"last_sync_summary": {
"total_payin": 100000.0,
"total_payout": 95000.0,
"net_profit": 5000.0,
"total_bets": 50,
"total_matches": 1,
"cap_compensation_balance": 5000.0
},
"server_timestamp": "2026-02-02T10:00:00.000Z"
}
```
**Response when client_id is NOT provided** (returns all clients for the authenticated user):
```json
{
"success": true,
"message": "Found 2 client(s) for this user",
"clients": [
{
"client_id": "client_123",
"last_sync_id": "sync_20260201_214327_abc12345",
"last_sync_timestamp": "2026-02-01T21:43:27.249Z",
"last_sync_type": "incremental",
"total_syncs": 5,
"requires_full_sync": false,
"last_sync_summary": {
"total_payin": 100000.0,
"total_payout": 95000.0,
"net_profit": 5000.0,
"total_bets": 50,
"total_matches": 1,
"cap_compensation_balance": 5000.0
}
},
{
"client_id": "client_456",
"last_sync_id": null,
"last_sync_timestamp": null,
"last_sync_type": null,
"total_syncs": 0,
"requires_full_sync": true,
"last_sync_summary": null
}
],
"server_timestamp": "2026-02-02T10:00:00.000Z"
}
```
**Usage**:
- Client can call this endpoint without parameters to get status for all its clients
- Client can check the `requires_full_sync` flag for each client to determine if a full sync is needed
- This is useful for monitoring sync status across multiple clients
### Error Response
```json
......
......@@ -1457,16 +1457,82 @@ def api_get_last_sync():
'details': 'Invalid or expired API token'
}), 401
# Get client_id from query parameter
# Get client_id from query parameter (optional)
client_id = request.args.get('client_id')
# If client_id is not provided, get all clients for this user
if not client_id:
# Get all clients associated with this user's API tokens
user_token_ids = [t.id for t in APIToken.query.filter_by(user_id=user.id).all()]
if user_token_ids:
client_ids = [c.rustdesk_id for c in ClientActivity.query.filter(
ClientActivity.api_token_id.in_(user_token_ids)
).all()]
if not client_ids:
return jsonify({
'success': False,
'error': 'Invalid request',
'details': 'client_id parameter is required'
}), 400
'success': True,
'message': 'No clients found for this user',
'clients': [],
'server_timestamp': datetime.utcnow().isoformat()
}), 200
# Return last sync info for all clients
clients_data = []
for cid in client_ids:
last_sync = ReportSync.query.filter_by(client_id=cid)\
.order_by(desc(ReportSync.sync_timestamp))\
.first()
if last_sync:
total_syncs = ReportSync.query.filter_by(client_id=cid).count()
clients_data.append({
'client_id': cid,
'last_sync_id': last_sync.sync_id,
'last_sync_timestamp': last_sync.sync_timestamp.isoformat() if last_sync.sync_timestamp else None,
'last_sync_type': last_sync.sync_type if hasattr(last_sync, 'sync_type') else 'unknown',
'total_syncs': total_syncs,
'last_sync_summary': {
'total_payin': float(last_sync.total_payin) if last_sync.total_payin else 0.0,
'total_payout': float(last_sync.total_payout) if last_sync.total_payout else 0.0,
'net_profit': float(last_sync.net_profit) if last_sync.net_profit else 0.0,
'total_bets': last_sync.total_bets,
'total_matches': last_sync.total_matches,
'cap_compensation_balance': float(last_sync.cap_compensation_balance) if last_sync.cap_compensation_balance else 0.0
}
})
else:
# Check if any records exist for this client
total_bets_for_client = Bet.query.filter_by(client_id=cid).count()
total_stats_for_client = ExtractionStats.query.filter_by(client_id=cid).count()
total_match_reports_for_client = MatchReport.query.filter_by(client_id=cid).count()
requires_full_sync = (total_bets_for_client == 0 and total_stats_for_client == 0 and total_match_reports_for_client == 0)
clients_data.append({
'client_id': cid,
'last_sync_id': None,
'last_sync_timestamp': None,
'last_sync_type': None,
'total_syncs': 0,
'requires_full_sync': requires_full_sync,
'last_sync_summary': None
})
return jsonify({
'success': True,
'message': f'Found {len(clients_data)} client(s) for this user',
'clients': clients_data,
'server_timestamp': datetime.utcnow().isoformat()
}), 200
else:
return jsonify({
'success': True,
'message': 'No API tokens found for this user',
'clients': [],
'server_timestamp': datetime.utcnow().isoformat()
}), 200
# Verify user has access to this client
# Verify user has access to this specific client
if not user.is_admin:
# Check if this client belongs to user's API tokens
user_token_ids = [t.id for t in APIToken.query.filter_by(user_id=user.id).all()]
......
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