move_detect/server/src/Repository/Pdo/PdoNonceRepository.php
유창욱 3f2823a764 refactor: rename cloud/ to server/ (cafe24 is web hosting, not cloud)
The PHP app runs on Cafe24 shared web hosting -- calling it 'cloud'
was misleading. Renames the directory, the edge client module, and
every doc/script/identifier that used the term.

- cloud/ -> server/ (incl. server/deploy/build_cafe24_package.sh paths)
- edge/cloudclient.py -> edge/serverclient.py, CloudClient -> ServerClient
- cloud/docs/cloud-deploy.md -> server/docs/server-deploy.md
- CLOUD_* env/config keys -> SERVER_*
- tests renamed to match

Tests: PHP 109 pass (4 skipped), pytest 167 pass. dist/ regenerated.
2026-07-20 11:30:52 +09:00

40 lines
1.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Repository\Pdo;
use App\Repository\NonceRepository;
use PDO;
/**
* `nonces.nonce` is the PRIMARY KEY (see schema.sql). IngestController already
* calls seen() before remember(), so under normal operation remember() never
* collides. Under a replay race (two requests with the same nonce arriving
* concurrently), the PK uniqueness constraint is the real guard: the losing
* remember() throws a PDOException on the duplicate key instead of silently
* overwriting -- closing the seen()/remember() TOCTOU flagged in the Task 7
* review. We deliberately do not catch/swallow that exception here.
*/
final class PdoNonceRepository implements NonceRepository
{
public function __construct(private PDO $pdo) {}
public function seen(string $nonce): bool
{
$st = $this->pdo->prepare('SELECT 1 FROM move_nonces WHERE nonce = ? LIMIT 1');
$st->execute([$nonce]);
return (bool)$st->fetchColumn();
}
public function remember(string $nonce, int $expiresAt): void
{
$st = $this->pdo->prepare('INSERT INTO move_nonces (nonce, expires_at) VALUES (?, ?)');
$st->execute([$nonce, $expiresAt]);
// Cron-less reap: Cafe24 has no scheduler, so expired nonces accumulate forever until
// the table bloats and inserts slow/fail. Opportunistically prune a bounded batch of
// already-expired rows (safe: seen() only cares about unexpired ones).
if (mt_rand(1, 100) === 1) {
$del = $this->pdo->prepare('DELETE FROM move_nonces WHERE expires_at < ? LIMIT 5000');
$del->execute([time()]);
}
}
}