This commit is contained in:
andre committed 2026-06-12 21:20:23 +01:00
1 parent e68d357177
commit 3e3382df57
65 files changed
+1836 -42

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
<?php
// Content endpoint for the id.andmore.pt / hub social-post generator (hub pull).
//
// Server-to-server, no session: the hub signs each request with the shared
// HUB_CONTENT_SECRET (.env; generated on id.andmore.pt → Plataformas →
// fitme3070 → "Gerar segredo de conteúdo"):
// X-Hub-Date: ISO-8601 timestamp (±10 min tolerance)
// X-Hub-Token: hash_hmac('sha256', date . path, secret)
//
// Response is the generic hub envelope. One item type:
// event — the weekly class schedule (dias da semana + as aulas nesses dias).
// Each recurring class is emitted as an event whose date_start is the
// next occurrence of that weekday/time, so the hub renders the day
// name automatically; the weekday is also given explicitly in fields.
//
// GET params: from=Y-m-d to=Y-m-d type=event (all optional; the schedule is
// recurring, so the date range is not used to filter).
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../config/functions.php';
header('Content-Type: application/json; charset=utf-8');
$secret = (string) env('HUB_CONTENT_SECRET', '');
if ($secret === '') {
http_response_code(503);
echo json_encode(['error' => 'HUB_CONTENT_SECRET não configurado nesta plataforma.']);
exit;
}
$date = (string) ($_SERVER['HTTP_X_HUB_DATE'] ?? '');
$token = (string) ($_SERVER['HTTP_X_HUB_TOKEN'] ?? '');
$path = (string) (parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?? '');
$ts = $date !== '' ? strtotime($date) : false;
if ($token === '' || $ts === false || abs(time() - $ts) > 600
|| !hash_equals(hash_hmac('sha256', $date . $path, $secret), $token)) {
http_response_code(403);
echo json_encode(['error' => 'Assinatura inválida.']);
exit;
}
session_write_close(); // not needed past auth; release the session lock
$type = (string) ($_GET['type'] ?? '');
// Optional category filter: ?category=segunda or ?categories=segunda,quarta.
// For this platform the category dimension is the weekday. Empty = all.
$cat_filter = array_filter(array_map(
fn($c) => strtolower(trim($c)),
preg_split('/[,]/', (string) ($_GET['categories'] ?? $_GET['category'] ?? ''))
));
// Weekday enum → display label and ISO day-of-week (1=Mon … 7=Sun).
$WEEKDAYS = [
'segunda' => ['Segunda-feira', 1],
'terça' => ['Terça-feira', 2],
'quarta' => ['Quarta-feira', 3],
'quinta' => ['Quinta-feira', 4],
'sexta' => ['Sexta-feira', 5],
'sábado' => ['Sábado', 6],
'domingo' => ['Domingo', 7],
];
$LOCALS = ['sala1' => 'Sala 1', 'sala2' => 'Sala 2', 'crossfit' => 'CrossFit'];
// Next occurrence (today or in the coming week) of a given weekday at $time.
$next_occurrence = static function (int $isoDow, string $time): DateTime {
$dt = new DateTime('today');
$diff = ($isoDow - (int) $dt->format('N') + 7) % 7; // 0 = today
if ($diff > 0) $dt->modify("+{$diff} day");
[$h, $m, $s] = array_pad(explode(':', $time), 3, '0');
$dt->setTime((int) $h, (int) $m, (int) $s);
return $dt;
};
$items = [];
// ── event: weekly class schedule ────────────────────────────────────────────
if ($type === '' || $type === 'event') {
// db_horario_list() already joins modalidades and orders segunda→domingo,
// then by start time; restrict to live modalidades (not soft-deleted).
$rows = $pdo->query("
SELECT h.id, h.dia_semana, h.local,
m.nome AS modalidade_nome,
TIME_FORMAT(h.hora_inicio, '%H:%i') AS hora_inicio_fmt,
TIME_FORMAT(h.hora_fim, '%H:%i') AS hora_fim_fmt
FROM horario h
JOIN modalidades m ON m.m_id = h.id_modalidade AND m.deleted_at IS NULL
ORDER BY FIELD(h.dia_semana,'segunda','terça','quarta','quinta','sexta','sábado','domingo'),
h.hora_inicio ASC
")->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $r) {
$dia = (string) $r['dia_semana'];
if (!isset($WEEKDAYS[$dia])) continue; // unknown / NULL weekday — skip
if ($cat_filter && !in_array($dia, $cat_filter, true)) continue; // category filter
[$diaLabel, $isoDow] = $WEEKDAYS[$dia];
$ini = (string) $r['hora_inicio_fmt'];
$fim = (string) $r['hora_fim_fmt'];
$hora = $fim !== '' ? ($ini . '–' . $fim) : $ini;
$local = $LOCALS[(string) $r['local']] ?? '';
$start = $next_occurrence($isoDow, $ini !== '' ? $ini . ':00' : '00:00:00');
$end = $fim !== '' ? $next_occurrence($isoDow, $fim . ':00') : null;
$fields = ['Dia' => $diaLabel, 'Hora' => $hora];
if ($local !== '') $fields['Local'] = $local;
$items[] = [
'id' => 'aula-' . (int) $r['id'],
'type' => 'event',
'title' => (string) $r['modalidade_nome'],
'subtitle' => $hora . ($local !== '' ? ' · ' . $local : ''),
'date_start' => $start->format('c'),
'date_end' => $end ? $end->format('c') : '',
'url' => base_url('home/horario'),
'category' => $dia, // machine key (weekday enum value)
'category_label' => $diaLabel, // human label
'fields' => $fields,
'media' => [],
];
}
}
// Distinct categories present, in item order (segunda→domingo), with counts —
// lets the hub offer a category filter without re-deriving it.
$categories = [];
foreach ($items as $it) {
$k = $it['category'];
if ($k === '') continue;
if (!isset($categories[$k])) {
$categories[$k] = ['key' => $k, 'label' => $it['category_label'], 'count' => 0];
}
$categories[$k]['count']++;
}
echo json_encode([
'app' => 'fitme3070',
'generated_at' => gmdate('c'),
'item_types' => array_values(array_unique(array_column($items, 'type'))),
'category_label'=> 'Dia da semana', // what the category dimension means here
'categories' => array_values($categories),
'items' => $items,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);