You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.2 KiB
124 lines
3.2 KiB
<?php |
|
|
|
declare(strict_types=1); |
|
|
|
namespace App\Service; |
|
|
|
use App\Entity\Event; |
|
use Psr\Cache\CacheItemPoolInterface; |
|
use Psr\Cache\InvalidArgumentException; |
|
|
|
/** |
|
* Read/write persisted magazine Nostr index events (kinds 30040) without callback-based relay I/O |
|
* on the request path. Updated by {@see MagazineRefresher} (via `app:prewarm` / cron, or explicit CLI use). |
|
*/ |
|
final class MagazineIndexStore |
|
{ |
|
private const ROOT_PREFIX = 'mroot_v1_'; |
|
private const CAT_PREFIX = 'mcat_v1_'; |
|
|
|
/** 30 days — we refresh on page load, TTL is a safety cap if sync stops working. */ |
|
private const PERSIST_TTL = 2_592_000; |
|
|
|
public function __construct( |
|
private readonly CacheItemPoolInterface $pool, |
|
) { |
|
} |
|
|
|
public function getRoot(string $npub, string $dTag): ?Event |
|
{ |
|
$item = $this->pool->getItem($this->rootKey($npub, $dTag)); |
|
if (!$item->isHit()) { |
|
return null; |
|
} |
|
|
|
return $this->unwrap($item->get()); |
|
} |
|
|
|
public function getCategory(string $slug): ?Event |
|
{ |
|
if ($slug === '') { |
|
return null; |
|
} |
|
$item = $this->pool->getItem($this->categoryKey($slug)); |
|
if (!$item->isHit()) { |
|
return null; |
|
} |
|
|
|
return $this->unwrap($item->get()); |
|
} |
|
|
|
/** |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function putRoot(string $npub, string $dTag, Event $event): void |
|
{ |
|
$item = $this->pool->getItem($this->rootKey($npub, $dTag)); |
|
$item->set(serialize($event)); |
|
$item->expiresAfter(self::PERSIST_TTL); |
|
$this->pool->save($item); |
|
} |
|
|
|
/** |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function putCategory(string $slug, Event $event): void |
|
{ |
|
if ($slug === '') { |
|
return; |
|
} |
|
$item = $this->pool->getItem($this->categoryKey($slug)); |
|
$item->set(serialize($event)); |
|
$item->expiresAfter(self::PERSIST_TTL); |
|
$this->pool->save($item); |
|
} |
|
|
|
/** |
|
* Remove a cached category index (NIP-09 / local invalidation). |
|
* |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function deleteCategory(string $slug): void |
|
{ |
|
if ($slug === '') { |
|
return; |
|
} |
|
$this->pool->deleteItem($this->categoryKey($slug)); |
|
} |
|
|
|
/** |
|
* Remove the cached root magazine index for this npub + d_tag. |
|
* |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function deleteRoot(string $npub, string $dTag): void |
|
{ |
|
$this->pool->deleteItem($this->rootKey($npub, $dTag)); |
|
} |
|
|
|
private function rootKey(string $npub, string $dTag): string |
|
{ |
|
return self::ROOT_PREFIX.hash('sha256', $npub."\0".$dTag); |
|
} |
|
|
|
/** |
|
* Category `d` / slug strings may contain colons (NIP-33 `a` segments); PSR-6 keys must not use `{}()/\@:`. |
|
*/ |
|
private function categoryKey(string $slug): string |
|
{ |
|
return self::CAT_PREFIX.hash('sha256', $slug); |
|
} |
|
|
|
private function unwrap(mixed $value): ?Event |
|
{ |
|
if (!\is_string($value) || $value === '') { |
|
return null; |
|
} |
|
$e = unserialize($value, ['allowed_classes' => [Event::class]]); |
|
if (!$e instanceof Event) { |
|
return null; |
|
} |
|
|
|
return $e; |
|
} |
|
}
|
|
|