Prepare secure config and reliable workout sync
This commit is contained in:
+60
-31
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user