256 КБ) | 422 (payload не объект). * Payload хранится как есть (opaque JSON), updated_at — серверное время. */ require __DIR__ . '/../lib/http.php'; require __DIR__ . '/../lib/db.php'; require __DIR__ . '/../lib/auth.php'; http_security_headers(); try { method_must('GET', 'PUT'); $pdo = db(); $user = require_user($pdo); if (($_SERVER['REQUEST_METHOD'] ?? '') === 'GET') { $stmt = $pdo->prepare( 'SELECT payload, updated_at FROM progress WHERE user_id = ?' ); $stmt->execute([$user['id']]); $row = $stmt->fetch(); if (!is_array($row)) { no_content_out(); } json_out([ 'payload' => json_decode((string)$row['payload']), 'updatedAt' => (string)$row['updated_at'], ]); } // PUT — мутирующий эндпоинт: нужен X-GoLearn-Client: web. require_client_header(); $body = read_json_object(); if (!isset($body->payload) || !is_object($body->payload)) { error_out(422, 'validation'); } $updatedAt = gmdate('Y-m-d\TH:i:s\Z'); $stmt = $pdo->prepare( 'INSERT INTO progress (user_id, payload, updated_at) VALUES (?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET payload = excluded.payload, updated_at = excluded.updated_at' ); $stmt->execute([ $user['id'], json_encode($body->payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $updatedAt, ]); json_out(['updatedAt' => $updatedAt]); } catch (Throwable $e) { error_log('progress: ' . $e->getMessage()); error_out(500, 'internal'); }