fixed bugs

This commit is contained in:
2025-12-27 20:48:44 +01:00
parent 6124e7e8fa
commit 2254ca90ff
8 changed files with 1958 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
<?php
// public/userarea/api/_bootstrap.php
// Auth via Laravel Sanctum personal access tokens (PDO only).
// Table: auth_personal_access_tokens
// Exposes: $pdo (PDO), $iduserlogin (int)
declare(strict_types=1);
// CORS for Flutter Web (dev server runs on a different port)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Authorization, Content-Type, Accept');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Max-Age: 86400');
// Preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../class/db-functions.php';
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
/**
* Get Authorization header (Apache/Nginx/CGI compatible)
*/
function getAuthorizationHeader(): ?string
{
if (function_exists('getallheaders')) {
$headers = getallheaders();
foreach ($headers as $k => $v) {
if (strtolower($k) === 'authorization') return $v;
}
}
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) return $_SERVER['HTTP_AUTHORIZATION'];
if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) return $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
return null;
}
/**
* Extract Bearer token
*/
function getBearerToken(): ?string
{
$auth = getAuthorizationHeader();
if (!$auth) return null;
if (preg_match('/Bearer\s+(.*)$/i', $auth, $m)) {
return trim($m[1]);
}
return null;
}
$bearer = getBearerToken();
if (!$bearer) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (missing Bearer token).']);
exit;
}
// Sanctum plain token format: "<id>|<plainTextToken>"
if (strpos($bearer, '|') === false) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid token format).']);
exit;
}
[$tokenId, $plainToken] = explode('|', $bearer, 2);
$tokenId = (int)$tokenId;
if ($tokenId <= 0 || $plainToken === '') {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid token).']);
exit;
}
// --- IMPORTANT: your table name ---
$tokensTable = 'auth_personal_access_tokens';
// Lookup token by id
$stmt = $pdo->prepare("
SELECT id, tokenable_id, token, expires_at
FROM {$tokensTable}
WHERE id = ?
LIMIT 1
");
$stmt->execute([$tokenId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (token not found).']);
exit;
}
// Optional expiry check
if (!empty($row['expires_at'])) {
$now = new DateTimeImmutable('now', new DateTimeZone('Europe/Rome'));
$exp = new DateTimeImmutable($row['expires_at'], new DateTimeZone('Europe/Rome'));
if ($exp <= $now) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (token expired).']);
exit;
}
}
// Sanctum stores SHA-256 hash of the plain token
$hash = hash('sha256', $plainToken);
if (!hash_equals((string)$row['token'], $hash)) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (token mismatch).']);
exit;
}
$iduserlogin = (int)$row['tokenable_id'];
if ($iduserlogin <= 0) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid user).']);
exit;
}
// Update last_used_at (best effort)
try {
$pdo->prepare("UPDATE {$tokensTable} SET last_used_at = NOW() WHERE id = ?")->execute([$tokenId]);
} catch (Throwable $e) {
// ignore
}
+175
View File
@@ -0,0 +1,175 @@
<?php
// public/userarea/api/my_lessons.php
// GET ?school_id=3&month=2025-12
declare(strict_types=1);
require_once __DIR__ . '/_bootstrap.php'; // gives $pdo, $iduserlogin, $authUser
try {
$school_id = isset($_GET['school_id']) ? (int)$_GET['school_id'] : 0;
if ($school_id <= 0) {
http_response_code(422);
echo json_encode(['success' => false, 'message' => 'Missing required parameter: school_id']);
exit;
}
$month = $_GET['month'] ?? (new DateTimeImmutable('now', new DateTimeZone('Europe/Rome')))->format('Y-m');
if (!preg_match('/^\d{4}-\d{2}$/', (string)$month)) {
http_response_code(422);
echo json_encode(['success' => false, 'message' => 'Invalid month format. Use YYYY-MM.']);
exit;
}
$tz = new DateTimeZone('Europe/Rome');
$monthDate = DateTimeImmutable::createFromFormat('Y-m', $month, $tz) ?: new DateTimeImmutable('now', $tz);
$startOfMonth = $monthDate->format('Y-m-01'); // inclusive
$endOfMonth = $monthDate->modify('first day of next month')->format('Y-m-d'); // exclusive
$prevMonth = $monthDate->modify('-1 month')->format('Y-m');
$nextMonth = $monthDate->modify('+1 month')->format('Y-m');
// --- Authorization: user must belong to that school ---
// Adjust if your authorization logic differs.
$stmt = $pdo->prepare("
SELECT 1
FROM user_schools
WHERE user_id = ?
AND school_id = ?
AND status = 'active'
LIMIT 1
");
$stmt->execute([$iduserlogin, $school_id]);
if (!$stmt->fetchColumn()) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Forbidden: user not allowed for this school.']);
exit;
}
// --- School info (same as your page) ---
$stmt = $pdo->prepare("
SELECT id, name, address_street, address_city, address_province
FROM schools
WHERE id = ?
LIMIT 1
");
$stmt->execute([$school_id]);
$school = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$school) {
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'School not found.']);
exit;
}
// --- Main query (ported from your PHP page) ---
$stmt = $pdo->prepare("
SELECT
sb.id as booking_id,
sb.status,
sb.order_id,
cs.id as session_id,
cs.session_date,
cs.start_time,
cs.end_time,
cs.room_name,
c.name as class_name,
ct.level,
o.available_entries,
o.available_recoveries
FROM session_bookings sb
JOIN class_sessions cs ON sb.session_id = cs.id
JOIN class_types ct ON cs.class_type_id = ct.id
JOIN classes c ON ct.class_id = c.id
JOIN orders o ON sb.order_id = o.id
WHERE sb.user_id = ?
AND cs.school_id = ?
AND cs.session_date >= ?
AND cs.session_date < ?
ORDER BY cs.session_date ASC, cs.start_time ASC
");
$stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Better 24h rule: compare against actual lesson start datetime
$now = new DateTimeImmutable('now', $tz);
$limit = $now->modify('+24 hours');
$lessons = [];
foreach ($rows as $r) {
$startDt = DateTimeImmutable::createFromFormat(
'Y-m-d H:i:s',
$r['session_date'] . ' ' . $r['start_time'],
$tz
);
if (!$startDt) {
// fallback if start_time is H:i
$startDt = DateTimeImmutable::createFromFormat(
'Y-m-d H:i',
$r['session_date'] . ' ' . substr((string)$r['start_time'], 0, 5),
$tz
);
}
$canModify = false;
if ($r['status'] === 'booked' && $startDt instanceof DateTimeImmutable) {
$canModify = ($startDt > $limit);
}
$lessons[] = [
'booking_id' => (int)$r['booking_id'],
'status' => (string)$r['status'],
'order_id' => (int)$r['order_id'],
'session' => [
'session_id' => (int)$r['session_id'],
'date' => (string)$r['session_date'],
'start_time' => (string)$r['start_time'],
'end_time' => (string)$r['end_time'],
'room_name' => $r['room_name'] !== null ? (string)$r['room_name'] : null,
'start_datetime_iso' => $startDt ? $startDt->format(DateTimeInterface::ATOM) : null,
],
'class' => [
'name' => (string)$r['class_name'],
'level' => ($r['level'] ?? null) ? (string)$r['level'] : null,
],
'wallet' => [
'available_entries' => (int)($r['available_entries'] ?? 0),
'available_recoveries' => (int)($r['available_recoveries'] ?? 0),
],
'can_reschedule' => $canModify,
'can_cancel' => $canModify,
'can_modify' => $canModify,
];
}
echo json_encode([
'success' => true,
'month' => $monthDate->format('Y-m'),
'prev_month' => $prevMonth,
'next_month' => $nextMonth,
'school' => [
'id' => (int)$school['id'],
'name' => (string)$school['name'],
'address_street' => $school['address_street'] ?? null,
'address_city' => $school['address_city'] ?? null,
'address_province' => $school['address_province'] ?? null,
'address_full' => trim(
($school['address_street'] ?? '') . ', ' .
($school['address_city'] ?? '') . ' ' .
($school['address_province'] ?? '')
),
],
'lessons' => $lessons
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Server error.',
'error' => $e->getMessage()
]);
}
+88
View File
@@ -0,0 +1,88 @@
<?php
// public/userarea/api/api_user_schools.php
// Returns ONLY the schools where the authenticated user is enrolled.
declare(strict_types=1);
require_once __DIR__ . '/_bootstrap.php'; // provides $pdo, $iduserlogin
try {
// Optional user info for greeting
$stmt = $pdo->prepare("SELECT first_name, avatar FROM auth_users WHERE id = ? LIMIT 1");
$stmt->execute([$iduserlogin]);
$user = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
// User schools only
$stmt = $pdo->prepare("
SELECT
s.id,
s.name,
s.logo,
s.address_street,
s.address_postal_code,
s.address_city,
s.address_province,
s.address_country
FROM user_schools us
JOIN schools s ON us.school_id = s.id
WHERE us.user_id = ?
AND us.status = 'active'
AND s.status = 'active'
ORDER BY s.name
");
$stmt->execute([$iduserlogin]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$schools = array_map(function (array $s): array {
$street = trim((string)($s['address_street'] ?? ''));
$zip = trim((string)($s['address_postal_code'] ?? ''));
$city = trim((string)($s['address_city'] ?? ''));
$prov = trim((string)($s['address_province'] ?? ''));
$cntry = trim((string)($s['address_country'] ?? ''));
$line2Parts = [];
if ($zip !== '') $line2Parts[] = $zip;
if ($city !== '') $line2Parts[] = $city;
$line2 = implode(' ', $line2Parts);
if ($prov !== '') $line2 .= ' (' . $prov . ')';
if ($cntry !== '') $line2 .= ' - ' . $cntry;
return [
'id' => (int)$s['id'],
'name' => (string)($s['name'] ?? ''),
'logo' => ($s['logo'] ?? null) !== null && trim((string)$s['logo']) !== '' ? (string)$s['logo'] : null,
'address_street' => $street !== '' ? $street : null,
'address_postal_code' => $zip !== '' ? $zip : null,
'address_city' => $city !== '' ? $city : null,
'address_province' => $prov !== '' ? $prov : null,
'address_country' => $cntry !== '' ? $cntry : null,
'address_full' => trim($street . ($street && $line2 ? ', ' : '') . $line2),
];
}, $rows);
$autoSelect = (count($schools) === 1);
$selectedSchoolId = $autoSelect ? (int)$schools[0]['id'] : null;
echo json_encode([
'success' => true,
'auto_select' => $autoSelect,
'selected_school_id' => $selectedSchoolId,
'user' => [
'id' => $iduserlogin,
'first_name' => $user['first_name'] ?? null,
'avatar' => $user['avatar'] ?? null,
],
'schools' => $schools,
'message' => empty($schools)
? 'No active schools assigned to this user. Please contact your school.'
: null
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Server error.',
'error' => $e->getMessage()
]);
}