Rename api/hub-content.php -> api/delete_hub-content.php so the old content-pull path returns 404, disabling the hub social-post endpoint without removing the implementation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
146 lines
6.1 KiB
PHP
146 lines
6.1 KiB
PHP
<?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);
|