Ctrl K
ComunidadGuías…

api/corrections.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php
/**
 * Achievement Corrections API
 *
 * Corrections are a layer on top of Analytics: per tournament, per player, they add or
 * subtract achievements (180s, tons, lollipops, high outs, short legs). They live on the
 * server so every browser sees the same thing — the register in each browser's IndexedDB
 * is never modified. Analytics applies them at load time.
 *
 *   GET  → { corrections: [ ... ] }  (all tournaments)
 *   POST { tournamentId, corrections: [ ... ] }
 *        → replaces every correction for that tournament. An empty list removes them.
 *
 * Stored in tournaments/corrections/corrections.json — inside the tournaments volume so
 * it survives container rebuilds, but in a subfolder so list-tournaments.php (which only
 * reads tournaments/*.json) never mistakes it for a tournament.
 */

// Check if API is enabled
require_once 'api-check.php';

header('Content-Type: application/json');
header('Cache-Control: no-store');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');

// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

$method = $_SERVER['REQUEST_METHOD'];
if ($method !== 'GET' && $method !== 'POST') {
    http_response_code(405);
    echo json_encode(['error' => 'Method not allowed']);
    exit;
}

$correctionsDir  = dirname(__DIR__) . '/tournaments/corrections/';
$correctionsFile = $correctionsDir . 'corrections.json';

/**
 * Decode the stored file. Missing or unreadable → no corrections.
 */
function readCorrections($contents) {
    $data = json_decode($contents ?: '', true);
    return (is_array($data) && isset($data['corrections']) && is_array($data['corrections']))
        ? $data['corrections']
        : [];
}

// ─── GET ─────────────────────────────────────────────────────────────────────
if ($method === 'GET') {
    $contents = is_file($correctionsFile) ? file_get_contents($correctionsFile) : '';
    echo json_encode(['corrections' => readCorrections($contents)]);
    exit;
}

// ─── POST ────────────────────────────────────────────────────────────────────
// Saving needs the API key when NEWTON_API_KEY is set; reading (GET) never does
require_api_key();

$input = file_get_contents('php://input');

// Corrections for one tournament are a few hundred bytes; anything this size is a mistake
if (strlen($input) > 1024 * 1024) {
    http_response_code(413);
    echo json_encode(['error' => 'Payload too large (max 1 MB)']);
    exit;
}

$payload = json_decode($input, true);
if (!is_array($payload)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid JSON payload']);
    exit;
}

$tournamentId = isset($payload['tournamentId']) ? (string)$payload['tournamentId'] : '';
if ($tournamentId === '' || strlen($tournamentId) > 200) {
    http_response_code(400);
    echo json_encode(['error' => 'Missing or invalid tournamentId']);
    exit;
}

if (!isset($payload['corrections']) || !is_array($payload['corrections']) || count($payload['corrections']) > 512) {
    http_response_code(400);
    echo json_encode(['error' => 'Missing or invalid corrections list']);
    exit;
}

/** Keep only integers from a list of values. */
function intList($list) {
    if (!is_array($list)) return [];
    return array_values(array_map('intval', array_filter(array_slice($list, 0, 100), 'is_numeric')));
}

// Normalize each record to the known shape — unknown fields are dropped
$incoming = [];
foreach ($payload['corrections'] as $c) {
    if (!is_array($c) || !isset($c['playerId'])) continue;
    $incoming[] = [
        'tournamentId' => $tournamentId,
        'playerId'     => (string)$c['playerId'],
        'playerName'   => isset($c['playerName']) ? mb_substr((string)$c['playerName'], 0, 200) : '',
        'oneEighties'  => isset($c['oneEighties']) ? (int)$c['oneEighties'] : 0,
        'tons'         => isset($c['tons']) ? (int)$c['tons'] : 0,
        'lollipops'    => isset($c['lollipops']) ? (int)$c['lollipops'] : 0,
        'highOuts'     => [
            'add'    => intList($c['highOuts']['add'] ?? []),
            'remove' => intList($c['highOuts']['remove'] ?? []),
        ],
        'shortLegs'    => [
            'add'    => intList($c['shortLegs']['add'] ?? []),
            'remove' => intList($c['shortLegs']['remove'] ?? []),
        ],
        'updatedAt'    => time(),
    ];
}

if (!is_dir($correctionsDir) && !@mkdir($correctionsDir, 0755, true)) {
    http_response_code(500);
    echo json_encode(['error' => 'Failed to create corrections directory']);
    exit;
}

// Read-modify-write under an exclusive lock, so two saves can't overwrite each other
$fh = @fopen($correctionsFile, 'c+');
if (!$fh) {
    http_response_code(500);
    echo json_encode(['error' => 'Corrections file is not writable']);
    exit;
}

flock($fh, LOCK_EX);

$existing = readCorrections(stream_get_contents($fh));
$kept = array_values(array_filter($existing, function ($c) use ($tournamentId) {
    return !isset($c['tournamentId']) || (string)$c['tournamentId'] !== $tournamentId;
}));
$all = array_merge($kept, $incoming);

ftruncate($fh, 0);
rewind($fh);
$written = fwrite($fh, json_encode(['corrections' => $all], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
fflush($fh);
flock($fh, LOCK_UN);
fclose($fh);

if ($written === false) {
    http_response_code(500);
    echo json_encode(['error' => 'Failed to save corrections']);
    exit;
}

echo json_encode(['success' => true, 'corrections' => $all]);