Browse Source

Better profile article list

imwald
Nuša Pukšič 4 days ago
parent
commit
a10901c3cc
  1. 127
      src/Controller/AuthorController.php

127
src/Controller/AuthorController.php

@ -30,6 +30,9 @@ use Symfony\Component\Serializer\SerializerInterface; @@ -30,6 +30,9 @@ use Symfony\Component\Serializer\SerializerInterface;
class AuthorController extends AbstractController
{
public function __construct(
private readonly LoggerInterface $logger
) {}
/**
* Reading List Index
@ -239,57 +242,45 @@ class AuthorController extends AbstractController @@ -239,57 +242,45 @@ class AuthorController extends AbstractController
$author = $redisCacheService->getMetadata($pubkey);
// Try to get cached view first
$cachedArticles = $viewStore->fetchUserArticles($pubkey);
$fromCache = false;
// Check if viewer is the author
$currentUser = $this->getUser();
$isOwnProfile = $currentUser && $currentUser->getUserIdentifier() === $npub;
if ($cachedArticles !== null) {
// Redis view data already matches template - just extract articles
$articles = [];
foreach ($cachedArticles as $baseObject) {
if (isset($baseObject['article'])) {
$articles[] = (object) $baseObject['article'];
}
}
$fromCache = true;
} else {
// Cache miss - query using search service
$articles = $articleSearch->findByPubkey($pubkey, 100, 0);
// Build and cache Redis views for next time
if (!empty($articles)) {
try {
$baseObjects = [];
foreach ($articles as $article) {
if ($article instanceof Article) {
$baseObjects[] = $viewFactory->articleBaseObject($article, $author);
// Query fresh Article entities (not cached view data)
// This ensures we have proper entities for filtering logic
$articles = $articleSearch->findByPubkey($pubkey, 100, 0);
// Filter and deduplicate articles at the entity level
$articles = $this->filterAndDeduplicateArticles($articles, $isOwnProfile);
// Build view objects for template from filtered entities
$viewData = [];
if (!empty($articles)) {
try {
foreach ($articles as $article) {
if ($article instanceof Article) {
$baseObject = $viewFactory->articleBaseObject($article, $author);
$normalized = $viewFactory->normalizeBaseObject($baseObject);
// Extract just the article data and convert to object
// This matches what the template expects (same format as old cache code)
if (isset($normalized['article'])) {
$viewData[] = (object) $normalized['article'];
}
}
if (!empty($baseObjects)) {
$viewStore->storeUserArticles($pubkey, $baseObjects);
}
} catch (\Exception $e) {
// Log but don't fail the request
error_log('Failed to cache user articles view: ' . $e->getMessage());
}
} catch (\Exception $e) {
$this->logger->error('Failed to build view objects', [
'error' => $e->getMessage()
]);
}
}
$fromCache = false;
// Get latest createdAt for dispatching fetch message
if (!empty($articles)) {
// Handle both Article entities and cached arrays
if (is_array($articles[0])) {
$latest = isset($articles[0]['article']['publishedAt'])
? strtotime($articles[0]['article']['publishedAt'])
: time();
} else if ($articles[0] instanceof Article) {
// Article entity
$latest = $articles[0]->getCreatedAt()->getTimestamp();
} else {
// Fallback
// Something went wrong upstream, use current time
$latest = time();
}
// Articles are now guaranteed to be Article entities
$latest = $articles[0]->getCreatedAt()->getTimestamp();
// Dispatch async message to fetch new articles since latest + 1
$messageBus->dispatch(new FetchAuthorArticlesMessage($pubkey, $latest + 1));
} else {
@ -297,17 +288,65 @@ class AuthorController extends AbstractController @@ -297,17 +288,65 @@ class AuthorController extends AbstractController
$messageBus->dispatch(new FetchAuthorArticlesMessage($pubkey, 0));
}
return $this->render('profile/author.html.twig', [
'author' => $author,
'npub' => $npub,
'pubkey' => $pubkey,
'articles' => $articles,
'articles' => $viewData, // Pass normalized view data to template
'is_author_profile' => true,
'from_cache' => $fromCache,
]);
}
/**
* Filter and deduplicate articles:
* - Hide drafts (kind 30024) unless viewing own profile
* - Show only the latest version per slug
* - Only handles Article entities (not cached arrays)
*/
private function filterAndDeduplicateArticles(array $articles, bool $isOwnProfile): array
{
$slugMap = [];
foreach ($articles as $article) {
// Only handle Article entities - no more mixed format handling
if (!$article instanceof Article) {
continue;
}
$kind = $article->getKind();
$slug = $article->getSlug();
$createdAt = $article->getCreatedAt();
// Skip drafts unless viewing own profile
if (!$isOwnProfile && $kind === KindsEnum::LONGFORM_DRAFT->value) {
continue;
}
// Skip if no slug
if (!$slug) {
continue;
}
// Keep only the latest version per slug
if (!isset($slugMap[$slug]) || $createdAt > $slugMap[$slug]['createdAt']) {
$slugMap[$slug] = [
'article' => $article,
'createdAt' => $createdAt
];
}
}
// Extract just the articles, sorted by creation date (newest first)
$filtered = array_column($slugMap, 'article');
usort($filtered, function($a, $b) {
return $b->getCreatedAt() <=> $a->getCreatedAt(); // Descending order
});
return $filtered;
}
/**
* Redirect from /p/{pubkey} to /p/{npub}
* @throws Exception

Loading…
Cancel
Save