Files
fitness/public/api/v1/plans.php
T

115 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/_bootstrap.php';
api_method('GET');
$planUuid = $_GET['id'] ?? null;
if ($planUuid !== null && (!is_string($planUuid) || !preg_match('/^[0-9a-f-]{36}$/i', $planUuid))) {
api_json(['error' => 'invalid_plan_id'], 400);
}
try {
$database = Database::getInstance();
$connection = $database->getConnection();
if ($planUuid !== null) {
$planQuery = $database->prepare(
'SELECT plan_uuid, slug, title_ar, summary_ar, goal, level, weeks_duration, source_notes, safety_notes_ar, content_revision FROM training_plans WHERE is_published = 1 AND plan_uuid = ? LIMIT 1'
);
$planQuery->bind_param('s', $planUuid);
} else {
$planQuery = $database->prepare(
'SELECT plan_uuid, slug, title_ar, summary_ar, goal, level, weeks_duration, source_notes, safety_notes_ar, content_revision FROM training_plans WHERE is_published = 1 ORDER BY title_ar'
);
}
$planQuery->execute();
$planResult = $planQuery->get_result();
$plans = [];
while ($plan = $planResult->fetch_assoc()) {
$plan['weeks_duration'] = (int) $plan['weeks_duration'];
$plan['content_revision'] = (int) $plan['content_revision'];
$plan['source_notes'] = json_decode($plan['source_notes'] ?? '[]', true) ?? [];
$plan['safety_notes_ar'] = json_decode($plan['safety_notes_ar'], true) ?? [];
$plans[$plan['plan_uuid']] = $plan;
}
$planQuery->close();
if ($planUuid !== null && !$plans) {
api_json(['error' => 'plan_not_found'], 404);
}
if (!$plans) {
header('Cache-Control: public, max-age=60, stale-while-revalidate=300');
api_json(['plans' => []]);
}
$planUuids = array_keys($plans);
$ids = array_fill(0, count($planUuids), '?');
$sessionQuery = $database->prepare(
'SELECT s.plan_id, p.plan_uuid, s.week_number, s.day_number, s.title_ar, s.session_type, s.duration_minutes, s.intensity, s.notes_ar, e.exercise_uuid, e.slug AS exercise_slug, e.title_ar AS exercise_title_ar, e.instructions_ar, e.target_muscles, e.equipment, e.gif_url, e.gif_poster_url, e.duration_seconds AS exercise_duration_seconds, e.repetitions, e.safety_notes_ar AS exercise_safety_notes_ar, alt.exercise_uuid AS alternative_exercise_uuid, se.sort_order AS exercise_sort_order, se.sets, se.reps, se.duration_seconds, se.rest_seconds FROM training_plan_sessions s JOIN training_plans p ON p.id = s.plan_id LEFT JOIN training_session_exercises se ON se.session_id = s.id LEFT JOIN exercises e ON e.id = se.exercise_id AND e.is_published = 1 LEFT JOIN exercises alt ON alt.id = e.alternative_exercise_id AND alt.is_published = 1 WHERE p.plan_uuid IN (' . implode(',', $ids) . ') AND p.is_published = 1 ORDER BY p.title_ar, s.week_number, s.day_number, se.sort_order'
);
$bindArguments = [str_repeat('s', count($planUuids))];
foreach ($planUuids as $index => $_planUuid) {
$bindArguments[] = &$planUuids[$index];
}
call_user_func_array([$sessionQuery, 'bind_param'], $bindArguments);
$sessionQuery->execute();
$sessionResult = $sessionQuery->get_result();
foreach ($plans as &$plan) {
$plan['sessions'] = [];
}
unset($plan);
while ($row = $sessionResult->fetch_assoc()) {
$uuid = $row['plan_uuid'];
$week = (int) $row['week_number'];
$day = (int) $row['day_number'];
$key = $week . ':' . $day;
if (!isset($plans[$uuid]['sessions'][$key])) {
$plans[$uuid]['sessions'][$key] = [
'week' => $week,
'day' => $day,
'title_ar' => $row['title_ar'],
'type' => $row['session_type'],
'duration_minutes' => (int) $row['duration_minutes'],
'intensity' => $row['intensity'],
'notes_ar' => $row['notes_ar'],
'exercises' => [],
];
}
if ($row['exercise_uuid'] !== null) {
$plans[$uuid]['sessions'][$key]['exercises'][] = [
'exercise_uuid' => $row['exercise_uuid'],
'slug' => $row['exercise_slug'],
'title_ar' => $row['exercise_title_ar'],
'instructions_ar' => json_decode($row['instructions_ar'], true) ?? [],
'target_muscles' => json_decode($row['target_muscles'], true) ?? [],
'equipment' => json_decode($row['equipment'], true) ?? [],
'gif_url' => $row['gif_url'],
'gif_poster_url' => $row['gif_poster_url'],
'duration_seconds' => $row['duration_seconds'] === null ? null : (int) $row['duration_seconds'],
'repetitions' => $row['repetitions'],
'safety_notes_ar' => json_decode($row['exercise_safety_notes_ar'] ?? '[]', true) ?? [],
'alternative_exercise_uuid' => $row['alternative_exercise_uuid'],
'prescription' => [
'sets' => $row['sets'] === null ? null : (int) $row['sets'],
'reps' => $row['reps'],
'duration_seconds' => $row['duration_seconds'] === null ? null : (int) $row['duration_seconds'],
'rest_seconds' => (int) $row['rest_seconds'],
],
];
}
}
$sessionQuery->close();
foreach ($plans as &$plan) {
$plan['sessions'] = array_values($plan['sessions']);
}
unset($plan);
header('Cache-Control: public, max-age=60, stale-while-revalidate=300');
api_json(['plans' => array_values($plans)]);
} catch (Throwable $exception) {
error_log('Training plans unavailable: ' . $exception->getMessage());
api_json(['error' => 'service_unavailable'], 503);
}