Fix: Auto-derive client_id from auth token when missing in reports sync

- If client_id is missing or null in sync request, derive it from ClientActivity table
- Look up most recent client activity for the API token to get rustdesk_id
- Use derived client_id throughout the sync process (ReportSync, Bet, ExtractionStats, MatchReport)
- Prevents database error: 'Column client_id cannot be null'
- Ensures server uses same client identifier that client uses
- Returns client_id_used in response for debugging
- Fixes issue where client sends null client_id but server needs valid identifier
parent cd883251
...@@ -1031,8 +1031,8 @@ def api_reports_sync(): ...@@ -1031,8 +1031,8 @@ def api_reports_sync():
'details': 'No JSON data provided' 'details': 'No JSON data provided'
}), 400 }), 400
# Validate required fields # Validate required fields (client_id can be derived from auth token if missing)
required_fields = ['sync_id', 'client_id', 'sync_timestamp', 'date_range', 'start_date', 'end_date', 'bets', 'extraction_stats', 'summary'] required_fields = ['sync_id', 'sync_timestamp', 'date_range', 'start_date', 'end_date', 'bets', 'extraction_stats', 'summary']
for field in required_fields: for field in required_fields:
if field not in data: if field not in data:
return jsonify({ return jsonify({
...@@ -1040,6 +1040,32 @@ def api_reports_sync(): ...@@ -1040,6 +1040,32 @@ def api_reports_sync():
'error': 'Invalid request format', 'error': 'Invalid request format',
'details': f'Missing required field: {field}' 'details': f'Missing required field: {field}'
}), 400 }), 400
# Also check that required fields are not None/null
if data[field] is None:
return jsonify({
'success': False,
'error': 'Invalid request format',
'details': f'Required field {field} cannot be null'
}), 400
# client_id is optional - if missing or null, derive from auth token
client_id = data.get('client_id')
if not client_id:
# Derive client_id from ClientActivity using API token
most_recent_client = ClientActivity.query.filter_by(api_token_id=api_token.id)\
.order_by(desc(ClientActivity.last_seen))\
.first()
if most_recent_client:
client_id = most_recent_client.rustdesk_id
logger.info(f"Reports sync: Auto-detected client_id={client_id} from API token {api_token.name} (ID: {api_token.id}) via ClientActivity")
else:
logger.warning(f"Reports sync: No client activity found for API token {api_token.name}")
return jsonify({
'success': False,
'error': 'Cannot identify client',
'details': 'No client activity found for this API token. Please ensure client has tracked activity first.'
}), 400
# Get cap compensation balance (optional field) # Get cap compensation balance (optional field)
cap_compensation_balance = data.get('cap_compensation_balance', 0.00) cap_compensation_balance = data.get('cap_compensation_balance', 0.00)
...@@ -1105,7 +1131,7 @@ def api_reports_sync(): ...@@ -1105,7 +1131,7 @@ def api_reports_sync():
# Create report sync record # Create report sync record
report_sync = ReportSync( report_sync = ReportSync(
sync_id=data['sync_id'], sync_id=data['sync_id'],
client_id=data['client_id'], client_id=client_id,
sync_timestamp=sync_timestamp, sync_timestamp=sync_timestamp,
date_range=data['date_range'], date_range=data['date_range'],
start_date=start_date, start_date=start_date,
...@@ -1175,7 +1201,7 @@ def api_reports_sync(): ...@@ -1175,7 +1201,7 @@ def api_reports_sync():
bet = Bet( bet = Bet(
uuid=bet_data['uuid'], uuid=bet_data['uuid'],
sync_id=report_sync.id, sync_id=report_sync.id,
client_id=data['client_id'], client_id=client_id,
fixture_id=bet_data['fixture_id'], fixture_id=bet_data['fixture_id'],
match_id=first_detail.get('match_id'), match_id=first_detail.get('match_id'),
match_number=first_detail.get('match_number'), match_number=first_detail.get('match_number'),
...@@ -1213,7 +1239,7 @@ def api_reports_sync(): ...@@ -1213,7 +1239,7 @@ def api_reports_sync():
# Check for duplicate stats (match_id + client_id) # Check for duplicate stats (match_id + client_id)
existing_stats = ExtractionStats.query.filter_by( existing_stats = ExtractionStats.query.filter_by(
match_id=stats_data['match_id'], match_id=stats_data['match_id'],
client_id=data['client_id'] client_id=client_id
).first() ).first()
if existing_stats: if existing_stats:
...@@ -1240,7 +1266,7 @@ def api_reports_sync(): ...@@ -1240,7 +1266,7 @@ def api_reports_sync():
bet_details_for_match = db.session.query( bet_details_for_match = db.session.query(
BetDetail.match_number BetDetail.match_number
).join(Bet).filter( ).join(Bet).filter(
Bet.client_id == data['client_id'], Bet.client_id == client_id,
BetDetail.match_id == stats_data['match_id'] BetDetail.match_id == stats_data['match_id']
).first() ).first()
...@@ -1266,7 +1292,7 @@ def api_reports_sync(): ...@@ -1266,7 +1292,7 @@ def api_reports_sync():
bet_details_for_match = db.session.query( bet_details_for_match = db.session.query(
BetDetail.match_number BetDetail.match_number
).join(Bet).filter( ).join(Bet).filter(
Bet.client_id == data['client_id'], Bet.client_id == client_id,
BetDetail.match_id == stats_data['match_id'] BetDetail.match_id == stats_data['match_id']
).first() ).first()
...@@ -1275,7 +1301,7 @@ def api_reports_sync(): ...@@ -1275,7 +1301,7 @@ def api_reports_sync():
extraction_stats = ExtractionStats( extraction_stats = ExtractionStats(
sync_id=report_sync.id, sync_id=report_sync.id,
client_id=data['client_id'], client_id=client_id,
match_id=stats_data['match_id'], match_id=stats_data['match_id'],
match_number=match_number, match_number=match_number,
fixture_id=stats_data['fixture_id'], fixture_id=stats_data['fixture_id'],
...@@ -1302,7 +1328,7 @@ def api_reports_sync(): ...@@ -1302,7 +1328,7 @@ def api_reports_sync():
match_reports_count = 0 match_reports_count = 0
for stats_data in data['extraction_stats']: for stats_data in data['extraction_stats']:
# Get client token name # Get client token name
client_activity = ClientActivity.query.filter_by(rustdesk_id=data['client_id']).first() client_activity = ClientActivity.query.filter_by(rustdesk_id=client_id).first()
client_token_name = client_activity.api_token.name if client_activity and client_activity.api_token else 'Unknown' client_token_name = client_activity.api_token.name if client_activity and client_activity.api_token else 'Unknown'
# Calculate winning/losing/pending bets from bet details # Calculate winning/losing/pending bets from bet details
...@@ -1319,7 +1345,7 @@ def api_reports_sync(): ...@@ -1319,7 +1345,7 @@ def api_reports_sync():
BetDetail.match_number, BetDetail.match_number,
func.count(BetDetail.id).label('count') func.count(BetDetail.id).label('count')
).join(Bet).filter( ).join(Bet).filter(
Bet.client_id == data['client_id'], Bet.client_id == client_id,
BetDetail.match_id == match_id BetDetail.match_id == match_id
).group_by(BetDetail.result, BetDetail.match_number) ).group_by(BetDetail.result, BetDetail.match_number)
...@@ -1342,7 +1368,7 @@ def api_reports_sync(): ...@@ -1342,7 +1368,7 @@ def api_reports_sync():
# Create MatchReport record # Create MatchReport record
match_report = MatchReport( match_report = MatchReport(
sync_id=report_sync.id, sync_id=report_sync.id,
client_id=data['client_id'], client_id=client_id,
client_token_name=client_token_name, client_token_name=client_token_name,
match_id=match_id, match_id=match_id,
match_number=match_number, match_number=match_number,
...@@ -1376,7 +1402,7 @@ def api_reports_sync(): ...@@ -1376,7 +1402,7 @@ def api_reports_sync():
# Log successful sync # Log successful sync
log_entry = ReportSyncLog( log_entry = ReportSyncLog(
sync_id=data['sync_id'], sync_id=data['sync_id'],
client_id=data['client_id'], client_id=client_id,
sync_timestamp=sync_timestamp, sync_timestamp=sync_timestamp,
operation_type='new_sync', operation_type='new_sync',
status='success', status='success',
...@@ -1403,13 +1429,13 @@ def api_reports_sync(): ...@@ -1403,13 +1429,13 @@ def api_reports_sync():
if api_token: if api_token:
api_token.update_last_used(request.remote_addr) api_token.update_last_used(request.remote_addr)
logger.info(f"Report sync {data['sync_id']} completed successfully from client {data['client_id']}: {bets_count} bets, {stats_count} stats ({stats_new} new, {stats_updated} updated)") logger.info(f"Report sync {data['sync_id']} completed successfully from client {client_id}: {bets_count} bets, {stats_count} stats ({stats_new} new, {stats_updated} updated)")
# Check if this was the first sync for this client (server database was empty) # Check if this was the first sync for this client (server database was empty)
# Check if ANY records exist for this client across all report tables # Check if ANY records exist for this client across all report tables
total_bets_for_client = Bet.query.filter_by(client_id=data['client_id']).count() total_bets_for_client = Bet.query.filter_by(client_id=client_id).count()
total_stats_for_client = ExtractionStats.query.filter_by(client_id=data['client_id']).count() total_stats_for_client = ExtractionStats.query.filter_by(client_id=client_id).count()
total_match_reports_for_client = MatchReport.query.filter_by(client_id=data['client_id']).count() total_match_reports_for_client = MatchReport.query.filter_by(client_id=client_id).count()
# If no records exist in any table, this is effectively a first sync and requires full sync # If no records exist in any table, this is effectively a first sync and requires full sync
requires_full_sync = (total_bets_for_client == 0 and total_stats_for_client == 0 and total_match_reports_for_client == 0) requires_full_sync = (total_bets_for_client == 0 and total_stats_for_client == 0 and total_match_reports_for_client == 0)
...@@ -1419,7 +1445,8 @@ def api_reports_sync(): ...@@ -1419,7 +1445,8 @@ def api_reports_sync():
'synced_count': bets_count + stats_count, 'synced_count': bets_count + stats_count,
'message': 'Report data synchronized successfully', 'message': 'Report data synchronized successfully',
'requires_full_sync': requires_full_sync, 'requires_full_sync': requires_full_sync,
'server_timestamp': datetime.utcnow().isoformat() 'server_timestamp': datetime.utcnow().isoformat(),
'client_id_used': client_id # Include the client_id that was used
}), 200 }), 200
except Exception as e: except Exception as e:
......
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