clone of github.com/decent-newsroom/newsroom
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.
 
 
 
 
 
 

303 lines
9.3 KiB

<?php
declare(strict_types=1);
namespace App\Service;
use App\Dto\CategoryDraft;
use App\Entity\Event;
use Doctrine\ORM\EntityManagerInterface;
use swentel\nostr\Key\Key;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Service for managing reading list drafts and published lists
*/
class ReadingListManager
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly TokenStorageInterface $tokenStorage,
private readonly RequestStack $requestStack,
private readonly ReadingListWorkflowService $workflowService,
) {}
/**
* Get all published reading lists for the current user
* @return array<array{id: int, title: string, summary: ?string, slug: string, createdAt: \DateTimeInterface, pubkey: string, articleCount: int}>
*/
public function getUserReadingLists(): array
{
$lists = [];
$user = $this->tokenStorage->getToken()?->getUser();
if (!$user) {
return [];
}
try {
$key = new Key();
$pubkeyHex = $key->convertToHex($user->getUserIdentifier());
} catch (\Throwable $e) {
return [];
}
$repo = $this->em->getRepository(Event::class);
$events = $repo->findBy(['kind' => 30040, 'pubkey' => $pubkeyHex], ['created_at' => 'DESC']);
$seenSlugs = [];
foreach ($events as $ev) {
if (!$ev instanceof Event) continue;
$tags = $ev->getTags();
$isReadingList = false;
$title = null;
$slug = null;
$summary = null;
$articleCount = 0;
foreach ($tags as $t) {
if (is_array($t)) {
if (($t[0] ?? null) === 'type' && ($t[1] ?? null) === 'reading-list') {
$isReadingList = true;
}
if (($t[0] ?? null) === 'title') {
$title = (string)$t[1];
}
if (($t[0] ?? null) === 'summary') {
$summary = (string)$t[1];
}
if (($t[0] ?? null) === 'd') {
$slug = (string)$t[1];
}
if (($t[0] ?? null) === 'a') {
$articleCount++;
}
}
}
if ($isReadingList) {
// Collapse by slug: keep only newest per slug
$keySlug = $slug ?: ('__no_slug__:' . $ev->getId());
if (isset($seenSlugs[$slug ?? $keySlug])) {
continue;
}
$seenSlugs[$slug ?? $keySlug] = true;
$lists[] = [
'id' => $ev->getId(),
'title' => $title ?: '(untitled)',
'summary' => $summary,
'slug' => $slug,
'createdAt' => $ev->getCreatedAt(),
'pubkey' => $ev->getPubkey(),
'articleCount' => $articleCount,
];
}
}
return $lists;
}
/**
* Get the current draft reading list from session
*/
public function getCurrentDraft(): ?CategoryDraft
{
$session = $this->requestStack->getSession();
$data = $session->get('read_wizard');
return $data instanceof CategoryDraft ? $data : null;
}
/**
* Get the currently selected reading list slug (or null for new draft)
*/
public function getSelectedListSlug(): ?string
{
$session = $this->requestStack->getSession();
return $session->get('selected_reading_list_slug');
}
/**
* Set which reading list is currently selected
*/
public function setSelectedListSlug(?string $slug): void
{
$session = $this->requestStack->getSession();
if ($slug === null) {
$session->remove('selected_reading_list_slug');
} else {
$session->set('selected_reading_list_slug', $slug);
}
}
/**
* Load an existing published reading list into the draft
*/
public function loadPublishedListIntoDraft(string $slug): ?CategoryDraft
{
$user = $this->tokenStorage->getToken()?->getUser();
if (!$user) {
return null;
}
try {
$key = new Key();
$pubkeyHex = $key->convertToHex($user->getUserIdentifier());
} catch (\Throwable $e) {
return null;
}
$repo = $this->em->getRepository(Event::class);
$events = $repo->findBy(['kind' => 30040, 'pubkey' => $pubkeyHex], ['created_at' => 'DESC']);
foreach ($events as $ev) {
if (!$ev instanceof Event) continue;
$tags = $ev->getTags();
$isReadingList = false;
$eventSlug = null;
// First pass: check if this is the right event
foreach ($tags as $t) {
if (is_array($t)) {
if (($t[0] ?? null) === 'd') {
$eventSlug = (string)$t[1];
}
if (($t[0] ?? null) === 'type' && ($t[1] ?? null) === 'reading-list') {
$isReadingList = true;
}
}
}
if ($isReadingList && $eventSlug === $slug) {
// Found it! Parse into CategoryDraft
$draft = new CategoryDraft();
$draft->slug = $slug;
foreach ($tags as $t) {
if (!is_array($t)) continue;
$tagName = $t[0] ?? null;
$tagValue = $t[1] ?? null;
match ($tagName) {
'title' => $draft->title = (string)$tagValue,
'summary' => $draft->summary = (string)$tagValue,
't' => $draft->tags[] = (string)$tagValue,
'a' => $draft->articles[] = (string)$tagValue,
default => null,
};
}
// Save to session
$session = $this->requestStack->getSession();
$session->set('read_wizard', $draft);
$this->setSelectedListSlug($slug);
return $draft;
}
}
return null;
}
/**
* Create a new draft reading list
*/
public function createNewDraft(): CategoryDraft
{
$draft = new CategoryDraft();
$draft->title = 'My Reading List';
$draft->slug = substr(bin2hex(random_bytes(6)), 0, 8);
// Initialize workflow
$this->workflowService->initializeDraft($draft);
$session = $this->requestStack->getSession();
$session->set('read_wizard', $draft);
$this->setSelectedListSlug(null); // null = new draft
return $draft;
}
/**
* Update draft metadata and advance workflow
*/
public function updateDraftMetadata(CategoryDraft $draft): void
{
$this->workflowService->updateMetadata($draft);
$session = $this->requestStack->getSession();
$session->set('read_wizard', $draft);
}
/**
* Add articles to draft and advance workflow
*/
public function addArticlesToDraft(CategoryDraft $draft): void
{
$this->workflowService->addArticles($draft);
$session = $this->requestStack->getSession();
$session->set('read_wizard', $draft);
}
/**
* Mark draft as ready for review
*/
public function markReadyForReview(CategoryDraft $draft): bool
{
$result = $this->workflowService->markReadyForReview($draft);
if ($result) {
$session = $this->requestStack->getSession();
$session->set('read_wizard', $draft);
}
return $result;
}
/**
* Get article coordinates for a specific reading list by slug
*/
public function getArticleCoordinatesForList(string $slug): array
{
$user = $this->tokenStorage->getToken()?->getUser();
if (!$user) {
return [];
}
try {
$key = new Key();
$pubkeyHex = $key->convertToHex($user->getUserIdentifier());
} catch (\Throwable $e) {
return [];
}
$repo = $this->em->getRepository(Event::class);
$events = $repo->findBy(['kind' => 30040, 'pubkey' => $pubkeyHex], ['created_at' => 'DESC']);
foreach ($events as $ev) {
if (!$ev instanceof Event) continue;
$eventSlug = null;
$isReadingList = false;
$articles = [];
foreach ($ev->getTags() as $t) {
if (!is_array($t)) continue;
if (($t[0] ?? null) === 'd') {
$eventSlug = (string)$t[1];
}
if (($t[0] ?? null) === 'type' && ($t[1] ?? null) === 'reading-list') {
$isReadingList = true;
}
if (($t[0] ?? null) === 'a') {
$articles[] = (string)$t[1];
}
}
if ($isReadingList && $eventSlug === $slug) {
return $articles;
}
}
return [];
}
}