The nobat724_front SEO audit produced three backend-side tasks that the frontend work is blocked on or that it only masks: - doctors-list-city-and-limit: implemented in the previous commit - blog-city-field: blog has no city column, so the city-scoped blog work on the public site is written but inert - cleanup-polluted-doctor-clinic-records: records named after a phone number or "test" reach public results; the site currently hides them with a noindex filter, which masks rather than fixes the data Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.6 KiB
افزودن شهر به لیست پزشکان + رفع سقف خاموش limit
پروژه
clinicpro (backend)
cross-repo: خروجی این endpoint را nobat724_front/app/sitemap.js مصرف میکند.
پرامپت همتا (بعد از این اجرا شود): nobat724_front/.claude/prompt/sitemap-simplify-with-city.md
زمینه
در ممیزی SEO سایت عمومی دو محدودیت این endpoint باعث دو باگ در sitemap شد:
۱. پاسخ لیست پزشکان فیلد شهر ندارد. سایت عمومی چند-دامنهای است (۳۵ دامنهٔ شهری) و باید بداند هر پزشک به کدام دامنه تعلق دارد. چون شهر در پاسخ نیست، sitemap دامنهٔ اصلی مجبور است ۳۵ بار جداگانه لیست را با city_id بگیرد و از کل کم کند تا بفهمد کدام پزشک شهر اختصاصی ندارد. تولید sitemap ریشه ~۱۳ ثانیه طول میکشد.
۲. پارامتر limit بیصدا به ۵۰ سقف میخورد. کلاینت limit=500 میفرستد، پاسخ ۵۰ رکورد است و هیچ نشانهای از سقفخوردن در پاسخ نیست. این باعث شد sitemap ماهها روی ۵۰ پزشک بریده بماند (حلقهٔ صفحهبندی وقتی items.length < limit بود متوقف میشد). سمت فرانت با تکیه بر meta.totalPages رفع شد، ولی رفتار خاموشِ API همچنان تله است.
فایلهای مرتبط
| فایل | نقش |
|---|---|
clinicpro/src/Doctor/Entity/Doctor.php |
toListArray() — شکل پاسخ لیست |
clinicpro/src/Doctor/Repository/DoctorRepository.php |
سقف limit در خطوط ۴۵ و ۱۴۲ |
clinicpro/src/Doctor/Controller/DoctorController.php |
route /api/v1/doctors خط ۲۵۲ |
clinicpro/docs/api/doctor.md |
مستندات — الزاماً بهروز شود |
وضعیت فعلی
src/Doctor/Entity/Doctor.php:540 — بدون شهر:
public function toListArray(array $schedules = []): array
{
$sf = $this->computeScheduleFields($schedules);
return [
'id' => (string) $this->id,
'uuid' => $this->uuid,
'name' => $this->name,
'gender' => $this->gender,
'degree' => $this->degree,
'img' => $this->images ?? [],
'specialties' => array_map(fn(Specialty $s) => [...], $this->specialties->toArray()),
'satisfaction' => $this->hasPublicRating() ? (string) $this->doctorRatePercentage : null,
'point' => $this->hasPublicRating() ? (string) $this->doctorRate : null,
'free_turn' => $sf['free_turn'],
'hours_of_work' => $sf['hours_of_work'],
'active' => $this->activeDoctorAppointment && $sf['has_schedule'],
'owner_status' => $this->ownerStatus,
];
}
src/Doctor/Repository/DoctorRepository.php:45 و :142 — سقف خاموش:
$limit = min(50, max(1, (int) ($filters['limit'] ?? 10)));
نکته: در پاسخ جزئیات پزشک (
toDetailArray) شهر داخلaddress[].cityهست، ولیcityوstateسطحبالا آرایهٔ خالی برمیگردند. سایت عمومی برای همین ازaddress[].city.idاستخراج میکند (nobat724_front/lib/domainHelpers.js→extractEntityCityId). این پرامپت آن رفتار را تغییر نمیدهد.
وظایف
۱. افزودن شهر به toListArray()
شهرِ پزشک از آدرسهایش میآید. شهر اولین آدرس (یا آدرس اصلی، اگر مفهوم آدرس اصلی وجود دارد) بهعنوان شهر پزشک برگردد — چون سایت عمومی هم برای canonical دقیقاً همین قاعده («یک شهر اصلی برای پزشک چند-شهری») را اعمال میکند.
'city' => $primaryAddress?->getCity() ? [
'id' => (string) $primaryAddress->getCity()->getId(),
'name' => $primaryAddress->getCity()->getName(),
] : null,
'state' => $primaryAddress?->getProvince() ? [
'id' => (string) $primaryAddress->getProvince()->getId(),
'name' => $primaryAddress->getProvince()->getName(),
] : null,
- شکل
{ id, name }باشد تا باcityدر پاسخ لیست کلینیکها یکسان باشد (آنجا آرایهای از همین شکل است). idرشته باشد — همراستا با بقیهٔ فیلدهای این متد.- پزشک بدون آدرس →
null(نه آرایهٔ خالی، تا با «شهر ندارد» تفکیکپذیر بماند). - N+1 نساز: آدرس/شهر/استان در همان کوئری
findWithFiltersباJOIN/addSelectبارگذاری شود، نه lazy per-doctor.
۲. شفافکردن سقف limit
سقف ۵۰ حفظ شود (محافظت از دیتابیس)، ولی دیگر خاموش نباشد:
- مقدار مؤثر
limitدرmetaبرگردد (اگر الان برنمیگردد) تا کلاینت بفهمد درخواستش کوتاه شده. - در
docs/api/doctor.mdصریح نوشته شود: «limitحداکثر ۵۰؛ مقادیر بزرگتر بیصدا به ۵۰ کاهش مییابند».
اگر تصمیم گرفتی سقف را برای مصرفکنندهٔ sitemap بالاتر ببری، آن را بهصورت یک حد جداگانه و مستند انجام بده — نه با حذف min().
۳. بهروزرسانی مستندات
clinicpro/docs/api/doctor.md برای GET /api/v1/doctors:
- فیلدهای جدید
cityوstateبا مثال واقعی JSON - رفتار و سقف
limit
نکات مهم
toListArray()را مصرفکنندگان دیگری هم دارند (پنل ادمین، اپ Tauri). فیلد اضافه میکنیم، فیلد موجود را تغییر نام یا حذف نمیکنیم — تغییر افزایشی و backward-compatible باشد.- بعد از تغییر، پاسخ واقعی را با یک پزشک دارای آدرس تست کن:
curl -s "https://clinic-pro.ir/api/v1/doctors?page=1&limit=5" | jq '.data.data[0] | {name, city, state}' - پزشک
beaca548-816f-4613-937d-360db01bd7c8شهرش یاسوج (city_id: 123) است — نمونهٔ خوبی برای تأیید. - Entity تغییر نمیکند (فقط متد سریالسازی) ⇒ migration لازم نیست.