Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../public/includes/init_pr_cl.php';
|
||||
|
||||
|
||||
$PUSHCFG = require __DIR__ . '/config.php';
|
||||
|
||||
function json_response(array $data, int $status = 200): never {
|
||||
http_response_code($status);
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth JSON (pas de redirect HTML)
|
||||
*/
|
||||
function require_auth_json(?array $roles = null): void {
|
||||
if (!is_logged_in()) {
|
||||
json_response(['ok'=>false, 'error'=>'auth_required'], 401);
|
||||
}
|
||||
if ($roles) {
|
||||
$role = strtolower((string)($_SESSION['role'] ?? ''));
|
||||
$allowed = array_map(fn($r)=>strtolower((string)$r), $roles);
|
||||
if (!in_array($role, $allowed, true)) {
|
||||
json_response(['ok'=>false, 'error'=>'forbidden'], 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function read_json_body(): array {
|
||||
$raw = file_get_contents('php://input');
|
||||
if (!$raw) return [];
|
||||
$data = json_decode($raw, true);
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
require_auth_json(['client','praticien','secretaire','admin']);
|
||||
|
||||
$compteId = (int)($_SESSION['compte_id'] ?? 0);
|
||||
if ($compteId <= 0) json_response(['ok'=>false,'error'=>'auth_required'], 401);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT msg_unread, rdv_unread FROM push_badge_counters WHERE compte_id = ?");
|
||||
$stmt->execute([$compteId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC) ?: ['msg_unread'=>0,'rdv_unread'=>0];
|
||||
|
||||
$msg = (int)($row['msg_unread'] ?? 0);
|
||||
$rdv = (int)($row['rdv_unread'] ?? 0);
|
||||
|
||||
json_response([
|
||||
'ok' => true,
|
||||
'msg_unread' => $msg,
|
||||
'rdv_unread' => $rdv,
|
||||
'total' => $msg + $rdv,
|
||||
]);
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Génère tes clés VAPID et colle-les ici
|
||||
'vapid_public' => getenv('PUSH_VAPID_PUBLIC') ?: '',
|
||||
'vapid_private' => getenv('PUSH_VAPID_PRIVATE') ?: '',
|
||||
|
||||
// Obligatoire pour VAPID (email ou URL)
|
||||
'vapid_subject' => 'mailto:contact@uslugi.fr',
|
||||
|
||||
// TTL push (secondes)
|
||||
'ttl' => 60 * 60 * 24, // 24h
|
||||
];
|
||||
Whitespace-only changes.
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: text/plain; charset=UTF-8');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
// Log erreurs PHP (utile sans SSH)
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('error_log', __DIR__ . '/webcron_error.log');
|
||||
|
||||
// ===== sécurité =====
|
||||
const CRON_TOKEN = 'e3e6ba5785a447c3f763a4ca4b8c36480df63a19';
|
||||
|
||||
if (!hash_equals(CRON_TOKEN, $_GET['token'] ?? '')) {
|
||||
http_response_code(403);
|
||||
exit("forbidden\n");
|
||||
}
|
||||
|
||||
// ===== anti double exécution =====
|
||||
$lockFile = __DIR__ . '/cron.lock';
|
||||
$lock = fopen($lockFile, 'c');
|
||||
if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) {
|
||||
exit("already running\n");
|
||||
}
|
||||
|
||||
// ===== log minimal =====
|
||||
file_put_contents(__DIR__ . '/webcron.log', date('Y-m-d H:i:s') . " run\n", FILE_APPEND);
|
||||
|
||||
// ===== lance ton worker =====
|
||||
require __DIR__ . '/worker.php';
|
||||
|
||||
echo "OK\n";
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
|
||||
// pas besoin d’être connecté pour lire la clé publique
|
||||
json_response([
|
||||
'ok' => true,
|
||||
'publicKey' => $PUSHCFG['vapid_public'] ?? '',
|
||||
]);
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// /push/push_endpoint.php
|
||||
declare(strict_types=1);
|
||||
|
||||
function push_queue_notification(
|
||||
PDO $pdo,
|
||||
string $targetRole,
|
||||
?int $targetReferenceId,
|
||||
?int $targetCompteId,
|
||||
string $type,
|
||||
string $title,
|
||||
string $body,
|
||||
?string $url = null,
|
||||
?array $data = null
|
||||
): ?int {
|
||||
// Sécurité / hygiène
|
||||
$title = trim($title);
|
||||
$body = trim($body);
|
||||
if ($title === '' || $body === '') return null;
|
||||
|
||||
$dataJson = $data ? json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO push_notifications
|
||||
(target_role, target_reference_id, target_compte_id, type, title, body, url, data_json, status, attempts, created_at)
|
||||
VALUES
|
||||
(:role, :ref_id, :compte_id, :type, :title, :body, :url, :data_json, 'pending', 0, NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
':role' => $targetRole,
|
||||
':ref_id' => $targetReferenceId,
|
||||
':compte_id' => $targetCompteId,
|
||||
':type' => $type,
|
||||
':title' => mb_substr($title, 0, 120),
|
||||
':body' => mb_substr($body, 0, 255),
|
||||
':url' => $url ? mb_substr($url, 0, 255) : null,
|
||||
':data_json' => $dataJson,
|
||||
]);
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers “RDV” : cible métier par reference_id (client_id / praticien_id)
|
||||
*/
|
||||
function resolve_compte_id(PDO $pdo, string $role, int $refId): int {
|
||||
$st = $pdo->prepare("SELECT id FROM compte WHERE role = ? AND reference_id = ? LIMIT 1");
|
||||
$st->execute([$role, $refId]);
|
||||
return (int)($st->fetchColumn() ?: 0);
|
||||
}
|
||||
|
||||
function push_rdv_to_client(PDO $pdo, int $clientId, string $title, string $body, string $url, array $data = []): ?int {
|
||||
$cid = resolve_compte_id($pdo, 'client', $clientId);
|
||||
return push_queue_notification($pdo, 'client', $clientId, ($cid>0?$cid:null), 'rdv', $title, $body, $url, $data);
|
||||
}
|
||||
|
||||
function push_rdv_to_praticien(PDO $pdo, int $praticienId, string $title, string $body, string $url, array $data = []): ?int {
|
||||
$cid = resolve_compte_id($pdo, 'praticien', $praticienId);
|
||||
return push_queue_notification($pdo, 'praticien', $praticienId, ($cid>0?$cid:null), 'rdv', $title, $body, $url, $data);
|
||||
}
|
||||
|
||||
function push_rdv_to_secretaire(PDO $pdo, int $secretaireCompteId, string $title, string $body, string $url, array $data = []): ?int {
|
||||
return push_queue_notification($pdo, 'secretaire', null, $secretaireCompteId, 'rdv', $title, $body, $url, $data);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function push_queue_notification(
|
||||
PDO $pdo,
|
||||
array $target, // ['compte_id'=>?, 'role'=>?, 'reference_id'=>?] (tu peux passer 1 ou 2 champs)
|
||||
string $type,
|
||||
string $title,
|
||||
string $body,
|
||||
?string $url = null,
|
||||
?array $data = null
|
||||
): int {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO push_notifications
|
||||
(target_role, target_reference_id, target_compte_id, type, title, body, url, data_json, status, created_at)
|
||||
VALUES
|
||||
(:role, :ref, :cid, :type, :title, :body, :url, :data, 'pending', NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
':role' => (string)($target['role'] ?? 'client'),
|
||||
':ref' => isset($target['reference_id']) ? (int)$target['reference_id'] : null,
|
||||
':cid' => isset($target['compte_id']) ? (int)$target['compte_id'] : null,
|
||||
':type' => $type,
|
||||
':title'=> $title,
|
||||
':body' => $body,
|
||||
':url' => $url,
|
||||
':data' => $data ? json_encode($data, JSON_UNESCAPED_UNICODE) : null,
|
||||
]);
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
|
||||
require_auth_json(['client','praticien','secretaire','admin']);
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
json_response(['ok'=>false,'error'=>'method_not_allowed'], 405);
|
||||
}
|
||||
|
||||
$input = read_json_body();
|
||||
|
||||
// Format attendu: { endpoint, keys:{p256dh,auth}, userAgent?, deviceLabel? }
|
||||
$endpoint = trim((string)($input['endpoint'] ?? ''));
|
||||
$keys = $input['keys'] ?? [];
|
||||
$p256dh = trim((string)($keys['p256dh'] ?? ''));
|
||||
$auth = trim((string)($keys['auth'] ?? ''));
|
||||
|
||||
if ($endpoint === '' || $p256dh === '' || $auth === '') {
|
||||
json_response(['ok'=>false,'error'=>'bad_params'], 400);
|
||||
}
|
||||
|
||||
$compteId = (int)($_SESSION['compte_id'] ?? 0);
|
||||
$role = strtolower((string)($_SESSION['role'] ?? ''));
|
||||
$referenceId = $_SESSION['reference_id'] ?? null;
|
||||
|
||||
$userAgent = trim((string)($input['userAgent'] ?? ($_SERVER['HTTP_USER_AGENT'] ?? '')));
|
||||
$deviceLabel = trim((string)($input['deviceLabel'] ?? ''));
|
||||
|
||||
try {
|
||||
// Upsert par endpoint (UNIQUE endpoint(255))
|
||||
$sql = "
|
||||
INSERT INTO push_subscriptions
|
||||
(compte_id, role, reference_id, endpoint, p256dh, auth, user_agent, device_label, is_active, last_seen_at)
|
||||
VALUES
|
||||
(:compte_id, :role, :reference_id, :endpoint, :p256dh, :auth, :ua, :label, 1, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
compte_id = VALUES(compte_id),
|
||||
role = VALUES(role),
|
||||
reference_id = VALUES(reference_id),
|
||||
p256dh = VALUES(p256dh),
|
||||
auth = VALUES(auth),
|
||||
user_agent = VALUES(user_agent),
|
||||
device_label = VALUES(device_label),
|
||||
is_active = 1,
|
||||
last_seen_at = NOW()
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':compte_id' => $compteId,
|
||||
':role' => $role,
|
||||
':reference_id' => $referenceId !== null ? (int)$referenceId : null,
|
||||
':endpoint' => $endpoint,
|
||||
':p256dh' => $p256dh,
|
||||
':auth' => $auth,
|
||||
':ua' => $userAgent !== '' ? $userAgent : null,
|
||||
':label' => $deviceLabel !== '' ? $deviceLabel : null,
|
||||
]);
|
||||
|
||||
json_response(['ok'=>true]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push subscribe error: '.$e->getMessage());
|
||||
json_response(['ok'=>false,'error'=>'server_error'], 500);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/_bootstrap.php';
|
||||
|
||||
require_auth_json(['client','praticien','secretaire','admin']);
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
json_response(['ok'=>false,'error'=>'method_not_allowed'], 405);
|
||||
}
|
||||
|
||||
$input = read_json_body();
|
||||
$endpoint = trim((string)($input['endpoint'] ?? ''));
|
||||
|
||||
if ($endpoint === '') {
|
||||
json_response(['ok'=>false,'error'=>'bad_params'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE push_subscriptions
|
||||
SET is_active = 0, updated_at = NOW()
|
||||
WHERE endpoint = :e
|
||||
AND compte_id = :cid
|
||||
");
|
||||
$stmt->execute([
|
||||
':e' => $endpoint,
|
||||
':cid' => (int)($_SESSION['compte_id'] ?? 0),
|
||||
]);
|
||||
|
||||
json_response(['ok'=>true]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push unsubscribe error: '.$e->getMessage());
|
||||
json_response(['ok'=>false,'error'=>'server_error'], 500);
|
||||
}
|
||||
+20980
File diff suppressed because it is too large.
Load diff
+183
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../includes/bootstrap_global.php';
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Minishlink\WebPush\WebPush;
|
||||
use Minishlink\WebPush\Subscription;
|
||||
|
||||
$cfg = require __DIR__ . '/config.php';
|
||||
|
||||
$auth = [
|
||||
'VAPID' => [
|
||||
'subject' => $cfg['vapid_subject'],
|
||||
'publicKey' => $cfg['vapid_public'],
|
||||
'privateKey' => $cfg['vapid_private'],
|
||||
],
|
||||
];
|
||||
|
||||
$webPush = new WebPush($auth);
|
||||
$webPush->setDefaultOptions([
|
||||
'TTL' => (int)($cfg['ttl'] ?? 86400),
|
||||
]);
|
||||
|
||||
/**
|
||||
* badgeCount = msg_unread + rdv_unread
|
||||
* table push_badge_counters est indexée par compte_id (PK)
|
||||
*/
|
||||
function computeBadgeCount(PDO $pdo, array $n): int
|
||||
{
|
||||
$cid = (int)($n['target_compte_id'] ?? 0);
|
||||
if ($cid <= 0) return 0;
|
||||
|
||||
$st = $pdo->prepare("SELECT msg_unread, rdv_unread FROM push_badge_counters WHERE compte_id = ? LIMIT 1");
|
||||
$st->execute([$cid]);
|
||||
$row = $st->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) return 0;
|
||||
|
||||
$total = (int)($row['msg_unread'] ?? 0) + (int)($row['rdv_unread'] ?? 0);
|
||||
return max(0, min(99, $total));
|
||||
}
|
||||
|
||||
// 1) lot pending
|
||||
$notifs = $pdo->query("
|
||||
SELECT *
|
||||
FROM push_notifications
|
||||
WHERE status = 'pending'
|
||||
ORDER BY id ASC
|
||||
LIMIT 50
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$notifs) exit(0);
|
||||
|
||||
foreach ($notifs as $n) {
|
||||
$nid = (int)$n['id'];
|
||||
|
||||
// 1bis) lock
|
||||
$lock = $pdo->prepare("
|
||||
UPDATE push_notifications
|
||||
SET status='sending'
|
||||
WHERE id=? AND status='pending'
|
||||
");
|
||||
$lock->execute([$nid]);
|
||||
if ($lock->rowCount() === 0) continue;
|
||||
|
||||
// 2) subscriptions cibles
|
||||
$where = ["is_active = 1"];
|
||||
$params = [];
|
||||
|
||||
if (!empty($n['target_compte_id'])) {
|
||||
$where[] = "compte_id = :cid";
|
||||
$params[':cid'] = (int)$n['target_compte_id'];
|
||||
} else {
|
||||
// fallback (normalement tu as toujours target_compte_id)
|
||||
$where[] = "role = :role";
|
||||
$params[':role'] = (string)$n['target_role'];
|
||||
|
||||
if (!empty($n['target_reference_id'])) {
|
||||
$where[] = "reference_id = :ref";
|
||||
$params[':ref'] = (int)$n['target_reference_id'];
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, endpoint, p256dh, auth
|
||||
FROM push_subscriptions
|
||||
WHERE " . implode(' AND ', $where) . "
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$subs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$subs) {
|
||||
$pdo->prepare("
|
||||
UPDATE push_notifications
|
||||
SET status='failed', attempts=attempts+1, last_error='no_subscriptions'
|
||||
WHERE id=?
|
||||
")->execute([$nid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3) payload
|
||||
$badgeCount = computeBadgeCount($pdo, $n);
|
||||
$payload = [
|
||||
'type' => $n['type'],
|
||||
'title' => $n['title'],
|
||||
'body' => $n['body'],
|
||||
'url' => $n['url'],
|
||||
'data' => $n['data_json'] ? json_decode((string)$n['data_json'], true) : null,
|
||||
'nid' => $nid,
|
||||
'badgeCount' => $badgeCount,
|
||||
];
|
||||
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
// map endpoint => subscription_id
|
||||
$subIdByEndpoint = [];
|
||||
|
||||
foreach ($subs as $s) {
|
||||
$endpoint = (string)$s['endpoint'];
|
||||
$subIdByEndpoint[$endpoint] = (int)$s['id'];
|
||||
|
||||
$subscription = Subscription::create([
|
||||
'endpoint' => $endpoint,
|
||||
'publicKey' => (string)$s['p256dh'],
|
||||
'authToken' => (string)$s['auth'],
|
||||
]);
|
||||
|
||||
$webPush->queueNotification($subscription, $payloadJson, ['urgency' => 'normal']);
|
||||
}
|
||||
|
||||
// 4) flush + delivery log
|
||||
$successCount = 0;
|
||||
$lastErr = null;
|
||||
|
||||
$insLog = $pdo->prepare("
|
||||
INSERT INTO push_delivery_log
|
||||
(notification_id, subscription_id, http_status, error_code, error_message, created_at)
|
||||
VALUES
|
||||
(:nid, :sid, :http, :ecode, :emsg, NOW())
|
||||
");
|
||||
|
||||
foreach ($webPush->flush() as $report) {
|
||||
$ok = $report->isSuccess();
|
||||
|
||||
$endpoint = (string)$report->getRequest()->getUri();
|
||||
$sid = $subIdByEndpoint[$endpoint] ?? null;
|
||||
$statusCode = $report->getResponse() ? $report->getResponse()->getStatusCode() : null;
|
||||
$reason = $ok ? null : (string)$report->getReason();
|
||||
|
||||
if ($ok) $successCount++;
|
||||
|
||||
if ($sid) {
|
||||
$insLog->execute([
|
||||
':nid' => $nid,
|
||||
':sid' => $sid,
|
||||
':http' => $statusCode,
|
||||
':ecode' => (!$ok && in_array((int)$statusCode, [404, 410], true)) ? 'gone' : null,
|
||||
':emsg' => $reason ? mb_substr($reason, 0, 255) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$ok && in_array((int)$statusCode, [404, 410], true)) {
|
||||
$pdo->prepare("UPDATE push_subscriptions SET is_active=0 WHERE endpoint=?")->execute([$endpoint]);
|
||||
}
|
||||
|
||||
if (!$ok && $reason) $lastErr = $reason;
|
||||
}
|
||||
|
||||
// 5) statut notification
|
||||
if ($successCount > 0) {
|
||||
$pdo->prepare("
|
||||
UPDATE push_notifications
|
||||
SET status='sent', sent_at=NOW(), attempts=attempts+1, last_error=NULL
|
||||
WHERE id=?
|
||||
")->execute([$nid]);
|
||||
} else {
|
||||
$pdo->prepare("
|
||||
UPDATE push_notifications
|
||||
SET status='failed', attempts=attempts+1, last_error=?
|
||||
WHERE id=?
|
||||
")->execute([mb_substr((string)$lastErr, 0, 255), $nid]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user