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():
'details': 'No JSON data provided'
}), 400
# Validate required fields
required_fields = ['sync_id', 'client_id', 'sync_timestamp', 'date_range', 'start_date', 'end_date', 'bets', 'extraction_stats', 'summary']
# Validate required fields (client_id can be derived from auth token if missing)
required_fields = ['sync_id', 'sync_timestamp', 'date_range', 'start_date', 'end_date', 'bets', 'extraction_stats', 'summary']
for field in required_fields:
if field not in data:
return jsonify({
......@@ -1040,6 +1040,32 @@ def api_reports_sync():
'error': 'Invalid request format',
'details': f'Missing required field: {field}'
}), 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)
cap_compensation_balance = data.get('cap_compensation_balance', 0.00)
......@@ -1105,7 +1131,7 @@ def api_reports_sync():
# Create report sync record
report_sync = ReportSync(
sync_id=data['sync_id'],
client_id=data['client_id'],
client_id=client_id,
sync_timestamp=sync_timestamp,
date_range=data['date_range'],
start_date=start_date,
......@@ -1175,7 +1201,7 @@ def api_reports_sync():
bet = Bet(
uuid=bet_data['uuid'],
sync_id=report_sync.id,
client_id=data['client_id'],
client_id=client_id,
fixture_id=bet_data['fixture_id'],
match_id=first_detail.get('match_id'),
match_number=first_detail.get('match_number'),
......@@ -1213,7 +1239,7 @@ def api_reports_sync():
# Check for duplicate stats (match_id + client_id)
existing_stats = ExtractionStats.query.filter_by(
match_id=stats_data['match_id'],
client_id=data['client_id']
client_id=client_id
).first()
if existing_stats:
......@@ -1240,7 +1266,7 @@ def api_reports_sync():
bet_details_for_match = db.session.query(
BetDetail.match_number
).join(Bet).filter(
Bet.client_id == data['client_id'],
Bet.client_id == client_id,
BetDetail.match_id == stats_data['match_id']
).first()
......@@ -1266,7 +1292,7 @@ def api_reports_sync():
bet_details_for_match = db.session.query(
BetDetail.match_number
).join(Bet).filter(
Bet.client_id == data['client_id'],
Bet.client_id == client_id,
BetDetail.match_id == stats_data['match_id']
).first()
......@@ -1275,7 +1301,7 @@ def api_reports_sync():
extraction_stats = ExtractionStats(
sync_id=report_sync.id,
client_id=data['client_id'],
client_id=client_id,
match_id=stats_data['match_id'],
match_number=match_number,
fixture_id=stats_data['fixture_id'],
......@@ -1302,7 +1328,7 @@ def api_reports_sync():
match_reports_count = 0
for stats_data in data['extraction_stats']:
# 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'
# Calculate winning/losing/pending bets from bet details
......@@ -1319,7 +1345,7 @@ def api_reports_sync():
BetDetail.match_number,
func.count(BetDetail.id).label('count')
).join(Bet).filter(
Bet.client_id == data['client_id'],
Bet.client_id == client_id,
BetDetail.match_id == match_id
).group_by(BetDetail.result, BetDetail.match_number)
......@@ -1342,7 +1368,7 @@ def api_reports_sync():
# Create MatchReport record
match_report = MatchReport(
sync_id=report_sync.id,
client_id=data['client_id'],
client_id=client_id,
client_token_name=client_token_name,
match_id=match_id,
match_number=match_number,
......@@ -1376,7 +1402,7 @@ def api_reports_sync():
# Log successful sync
log_entry = ReportSyncLog(
sync_id=data['sync_id'],
client_id=data['client_id'],
client_id=client_id,
sync_timestamp=sync_timestamp,
operation_type='new_sync',
status='success',
......@@ -1403,13 +1429,13 @@ def api_reports_sync():
if api_token:
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 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_stats_for_client = ExtractionStats.query.filter_by(client_id=data['client_id']).count()
total_match_reports_for_client = MatchReport.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=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
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():
'synced_count': bets_count + stats_count,
'message': 'Report data synchronized successfully',
'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
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