feat(blog): enhance blog identifier resolution to support multiple identifiers and improve URL handling

This commit is contained in:
hamed
2026-08-09 07:14:16 +03:30
parent f6ef086589
commit dcf9285467
4 changed files with 164 additions and 3 deletions
+4 -1
View File
@@ -193,7 +193,10 @@ class BlogController extends BaseController
#[Route('/api/v1/blog/{slug}', methods: ['GET'])]
public function detail(string $slug, Request $request): JsonResponse
{
$blog = $this->blogRepo->findBySlug($slug) ?? $this->blogRepo->findByUuid($slug);
// Any identifier the post has ever been addressed by: slug, uuid, topic_slug,
// or the uuid prefix carried in an outdated slug. The response always reports
// the canonical slug, so the site can redirect an outdated URL instead of 404.
$blog = $this->blogRepo->findByPublicIdentifier($slug);
if ($blog === null || $blog->getStatus() !== Blog::STATUS_PUBLISHED) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'مقاله یافت نشد', 404);
}
+35
View File
@@ -14,6 +14,41 @@ class BlogRepository extends ServiceEntityRepository
public function findBySlug(string $slug): ?Blog { return $this->findOneBy(['slug' => $slug]); }
public function findByTopicSlug(string $topicSlug): ?Blog { return $this->findOneBy(['topicSlug' => $topicSlug]); }
/**
* Resolve a post from any public identifier that has ever addressed it.
*
* A post's slug is derived from its title, so editing the title changes the URL
* and every previously published link 404s. The stable parts survive: the uuid,
* the pipeline's topic_slug, and the 8-char uuid prefix that every generated
* slug carries as its suffix. Callers compare the returned slug with the
* requested one and redirect when they differ.
*/
public function findByPublicIdentifier(string $identifier): ?Blog
{
return $this->findBySlug($identifier)
?? $this->findByUuid($identifier)
?? $this->findByTopicSlug($identifier)
?? $this->findBySlugUuidSuffix($identifier);
}
/**
* Match on the 8-char uuid prefix that Blog::generateSlug() appends to every slug,
* so a renamed post is still reachable through its old URL.
*/
private function findBySlugUuidSuffix(string $slug): ?Blog
{
$suffix = substr($slug, strrpos($slug, '-') === false ? 0 : strrpos($slug, '-') + 1);
if (!preg_match('/^[0-9a-f]{8}$/i', $suffix)) {
return null;
}
return $this->createQueryBuilder('b')
->where('b.uuid LIKE :prefix')
->setParameter('prefix', $suffix . '-%')
->setMaxResults(1)
->getQuery()->getOneOrNullResult();
}
/**
* The admin review queue: posts awaiting a doctor's decision, newest first.
* @return Blog[]