feat(blog): add city_id to blogs for city-specific scoping

- Introduced a new nullable city_id column in the blogs table to allow scoping of blog posts to specific cities.
- Updated Blog entity to include a ManyToOne relationship with the City entity.
- Enhanced BlogController to handle city_id in the request, allowing filtering of posts by city.
- Modified BlogRepository to support querying published posts based on city_id.
- Added tests to ensure correct behavior for city-scoped and nationwide posts, including creation and updating of posts with city associations.
This commit is contained in:
hamed
2026-07-19 08:23:57 +03:30
parent bd66b213c2
commit a4b07c2f80
10 changed files with 535 additions and 178 deletions
+21 -2
View File
@@ -14,9 +14,11 @@ class BlogRepository extends ServiceEntityRepository
public function findBySlug(string $slug): ?Blog { return $this->findOneBy(['slug' => $slug]); }
/** @return Blog[] published, newest first */
public function findPublished(int $page = 1, int $limit = 20, ?string $tag = null): array
public function findPublished(int $page = 1, int $limit = 20, ?string $tag = null, ?int $cityId = null): array
{
$qb = $this->createQueryBuilder('b')
->leftJoin('b.city', 'c')
->addSelect('c')
->where('b.status = :status')
->setParameter('status', Blog::STATUS_PUBLISHED)
->orderBy('b.createdAt', 'DESC')
@@ -24,11 +26,12 @@ class BlogRepository extends ServiceEntityRepository
->setMaxResults($limit);
$this->applyTagFilter($qb, $tag);
$this->applyCityFilter($qb, $cityId);
return $qb->getQuery()->getResult();
}
public function countPublished(?string $tag = null): int
public function countPublished(?string $tag = null, ?int $cityId = null): int
{
$qb = $this->createQueryBuilder('b')
->select('COUNT(b.id)')
@@ -36,10 +39,26 @@ class BlogRepository extends ServiceEntityRepository
->setParameter('status', Blog::STATUS_PUBLISHED);
$this->applyTagFilter($qb, $tag);
$this->applyCityFilter($qb, $cityId);
return (int) $qb->getQuery()->getSingleScalarResult();
}
/**
* دامنهٔ یک شهر باید پست‌های همان شهر **و** پست‌های سراسری را ببیند — پست
* سراسری (city NULL) روی همهٔ دامنه‌ها منتشر است، فقط canonicalش روی دامنهٔ اصلی
* می‌نشیند. بدون شرط NULL، دامنه‌های شهری محتوای عمومی را از دست می‌دادند.
*/
private function applyCityFilter(\Doctrine\ORM\QueryBuilder $qb, ?int $cityId): void
{
if ($cityId === null) {
return;
}
$qb->andWhere('b.city = :cityId OR b.city IS NULL')
->setParameter('cityId', $cityId);
}
private function applyTagFilter(\Doctrine\ORM\QueryBuilder $qb, ?string $tag): void
{
if ($tag === null || $tag === '') {