Prepare secure config and reliable workout sync

This commit is contained in:
Hamza-Ayed
2026-10-04 00:33:44 +03:00
parent 05f1c9ec6b
commit e7f3c777d1
17 changed files with 553 additions and 119 deletions
+60 -31
View File
@@ -85,13 +85,14 @@ try {
// Prepare workout insert statement
$stmt = $db->prepare('
INSERT INTO workouts (
workout_uuid, user_id, workout_type, distance_meters,
workout_uuid, user_id, client_workout_uuid, workout_type, distance_meters,
duration_seconds, elevation_gain_meters, elevation_loss_meters,
calories_burned, average_pace_mps, max_speed_mps,
route_polyline, coordinate_count, start_lat, start_lng,
end_lat, end_lng, start_time, end_time, weather_condition,
temperature_celsius, notes, is_public
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)
');
// Extract start and end coordinates
@@ -102,12 +103,16 @@ try {
$average_pace = $payload['duration_seconds'] > 0
? $payload['distance_meters'] / $payload['duration_seconds']
: 0;
$client_workout_uuid = $payload['client_workout_id'] ?? null;
$start_time_utc = normalizeUtcDateTime($payload['start_time']);
$end_time_utc = normalizeUtcDateTime($payload['end_time']);
// Bind parameters
$stmt->bind_param(
'sisissiiiddidddssdssi',
'sissiiiidddsiddddsssdsi',
$workout_uuid,
$user_id,
$client_workout_uuid,
$payload['workout_type'],
$payload['distance_meters'],
$payload['duration_seconds'],
@@ -122,8 +127,8 @@ try {
$start_coord['lng'],
$end_coord['lat'],
$end_coord['lng'],
$payload['start_time'],
$payload['end_time'],
$start_time_utc,
$end_time_utc,
$payload['weather_condition'],
$payload['temperature_celsius'],
$payload['notes'],
@@ -134,11 +139,25 @@ try {
throw new Exception('Failed to insert workout: ' . $stmt->error, 500);
}
$was_inserted = $stmt->affected_rows === 1;
$workout_id = $db->getLastInsertId();
$stmt->close();
if (!$was_inserted) {
$existing_stmt = $db->prepare('SELECT workout_uuid FROM workouts WHERE id = ? AND user_id = ?');
$existing_stmt->bind_param('ii', $workout_id, $user_id);
$existing_stmt->execute();
$existing_result = $existing_stmt->get_result();
$existing_workout = $existing_result->fetch_assoc();
$existing_stmt->close();
if (!$existing_workout) {
throw new Exception('Unable to confirm idempotent workout submission', 500);
}
$workout_uuid = $existing_workout['workout_uuid'];
}
// Process and store segments if provided
if (!empty($payload['segments'])) {
if ($was_inserted && !empty($payload['segments'])) {
$segment_stmt = $db->prepare('
INSERT INTO workout_segments (workout_id, segment_order, duration_seconds, distance_meters, average_pace_mps, index_in_polyline)
VALUES (?, ?, ?, ?, ?, ?)
@@ -168,45 +187,49 @@ try {
}
// Update or create user stats cache
updateUserStatsCache($db, $user_id);
if ($was_inserted) {
updateUserStatsCache($db, $user_id);
}
// Log successful submission
$logStmt = $db->prepare('
INSERT INTO api_logs (user_id, endpoint, method, status_code, ip_address, user_agent, response_time_ms)
VALUES (?, ?, ?, ?, ?, ?, ?)
');
if ($was_inserted) {
$logStmt = $db->prepare('
INSERT INTO api_logs (user_id, endpoint, method, status_code, ip_address, user_agent, response_time_ms)
VALUES (?, ?, ?, ?, ?, ?, ?)
');
$endpoint = '/api/v1/workouts';
$method = 'POST';
$status = 201;
$ip = getClientIpAddress();
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
$response_time = (int)((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000);
$endpoint = '/api/v1/workouts';
$method = 'POST';
$status = 201;
$ip = getClientIpAddress();
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
$response_time = (int)((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000);
$logStmt->bind_param(
'ississi',
$user_id,
$endpoint,
$method,
$status,
$ip,
$user_agent,
$response_time
);
$logStmt->execute();
$logStmt->close();
$logStmt->bind_param(
'ississi',
$user_id,
$endpoint,
$method,
$status,
$ip,
$user_agent,
$response_time
);
$logStmt->execute();
$logStmt->close();
}
// Commit transaction
$db->commit();
// Return success response
http_response_code(201);
http_response_code($was_inserted ? 201 : 200);
echo json_encode([
'status' => 'success',
'data' => [
'workout_id' => $workout_id,
'workout_uuid' => $workout_uuid,
'message' => 'Workout submitted successfully',
'message' => $was_inserted ? 'Workout submitted successfully' : 'Workout was already received',
'timestamp' => date('c')
]
]);
@@ -270,6 +293,12 @@ function generateUUID() {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
}
/** Store ISO-8601 client timestamps as UTC MySQL DATETIME values. */
function normalizeUtcDateTime($value) {
$date = new DateTimeImmutable($value);
return $date->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
}
/**
* Update user stats cache
*/