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:
@@ -5,6 +5,7 @@ namespace App\Blog\Controller;
|
||||
use App\Auth\Entity\User;
|
||||
use App\Blog\Entity\Blog;
|
||||
use App\Blog\Repository\BlogRepository;
|
||||
use App\Location\Repository\CityRepository;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use App\Shared\Service\FileValidatorService;
|
||||
@@ -20,10 +21,31 @@ class BlogController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BlogRepository $blogRepo,
|
||||
private readonly CityRepository $cityRepo,
|
||||
private readonly FileValidatorService $fileValidator,
|
||||
private readonly string $projectDir,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* city_id ورودی ادمین را به Entity تبدیل میکند.
|
||||
* مقدار خالی/صفر/null یعنی «سراسری» و عمداً به null نگاشت میشود.
|
||||
*
|
||||
* @throws \App\Shared\Exception\AppException وقتی شناسهٔ شهر نامعتبر باشد
|
||||
*/
|
||||
private function resolveCity(mixed $cityId): ?\App\Location\Entity\City
|
||||
{
|
||||
if ($cityId === null || $cityId === '' || (int) $cityId === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$city = $this->cityRepo->find((int) $cityId);
|
||||
if ($city === null) {
|
||||
throw new \App\Shared\Exception\AppException(ErrorCodes::ERR_VALIDATION_002, 'شهر یافت نشد', 422);
|
||||
}
|
||||
|
||||
return $city;
|
||||
}
|
||||
|
||||
// ── Public list/detail ────────────────────────────────────────────────────
|
||||
|
||||
#[OA\Get(
|
||||
@@ -32,6 +54,13 @@ class BlogController extends BaseController
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)),
|
||||
new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 20, maximum: 50)),
|
||||
new OA\Parameter(
|
||||
name: 'city_id',
|
||||
in: 'query',
|
||||
required: false,
|
||||
description: 'Scope to one city: returns that city\'s posts plus nationwide posts (city_id IS NULL). Omit to return every published post.',
|
||||
schema: new OA\Schema(type: 'integer')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -58,12 +87,18 @@ class BlogController extends BaseController
|
||||
#[Route('/api/v1/blogs', methods: ['GET'])]
|
||||
public function list(Request $request): JsonResponse
|
||||
{
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(50, max(1, (int) $request->query->get('limit', 20)));
|
||||
$tag = $request->query->get('tag') ?: null;
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(50, max(1, (int) $request->query->get('limit', 20)));
|
||||
$tag = $request->query->get('tag') ?: null;
|
||||
$cityId = $request->query->get('city_id') !== null
|
||||
? max(1, (int) $request->query->get('city_id'))
|
||||
: null;
|
||||
|
||||
$blogs = array_map(fn(Blog $b) => $b->toListArray(), $this->blogRepo->findPublished($page, $limit, $tag));
|
||||
$total = $this->blogRepo->countPublished($tag);
|
||||
$blogs = array_map(
|
||||
fn(Blog $b) => $b->toListArray(),
|
||||
$this->blogRepo->findPublished($page, $limit, $tag, $cityId)
|
||||
);
|
||||
$total = $this->blogRepo->countPublished($tag, $cityId);
|
||||
|
||||
return $this->paginated($blogs, $total, $page, $limit);
|
||||
}
|
||||
@@ -127,6 +162,7 @@ class BlogController extends BaseController
|
||||
new OA\Property(property: 'summary', type: 'string', nullable: true),
|
||||
new OA\Property(property: 'tags', type: 'array', items: new OA\Items(type: 'string'), nullable: true),
|
||||
new OA\Property(property: 'status', type: 'string', enum: ['draft', 'published'], nullable: true),
|
||||
new OA\Property(property: 'city_id', type: 'integer', nullable: true, description: 'City this post belongs to. Omit or null for a nationwide post.'),
|
||||
]
|
||||
)
|
||||
),
|
||||
@@ -190,6 +226,8 @@ class BlogController extends BaseController
|
||||
if (!empty($data['tags'])) $blog->setTags((array)$data['tags']);
|
||||
if (!empty($data['status'])) $blog->setStatus($data['status']);
|
||||
if (!empty($data['image_url'])) $blog->setImageUrl($data['image_url']);
|
||||
// نبودِ city_id یعنی سراسری — پس همیشه اعمال میشود، نه فقط وقتی مقدار دارد.
|
||||
$blog->setCity($this->resolveCity($data['city_id'] ?? null));
|
||||
|
||||
// Ensure slug uniqueness
|
||||
if ($this->blogRepo->findBySlug($blog->getSlug()) !== null) {
|
||||
@@ -214,6 +252,7 @@ class BlogController extends BaseController
|
||||
new OA\Property(property: 'summary', type: 'string', nullable: true),
|
||||
new OA\Property(property: 'tags', type: 'array', items: new OA\Items(type: 'string'), nullable: true),
|
||||
new OA\Property(property: 'status', type: 'string', enum: ['draft', 'published'], nullable: true),
|
||||
new OA\Property(property: 'city_id', type: 'integer', nullable: true, description: 'City this post belongs to. Omit or null for a nationwide post.'),
|
||||
]
|
||||
)
|
||||
),
|
||||
@@ -263,6 +302,8 @@ class BlogController extends BaseController
|
||||
if (array_key_exists('tags', $data)) $blog->setTags((array)$data['tags']);
|
||||
if (array_key_exists('status', $data)) $blog->setStatus($data['status']);
|
||||
if (array_key_exists('image_url', $data)) $blog->setImageUrl($data['image_url'] ?: null);
|
||||
// PATCH: فقط وقتی صریحاً فرستاده شد تغییر کند. ارسال null یعنی «سراسریاش کن».
|
||||
if (array_key_exists('city_id', $data)) $blog->setCity($this->resolveCity($data['city_id']));
|
||||
|
||||
$this->blogRepo->save($blog);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user