Files
clinicpro/.claude/prompt/doctors-list-city-and-limit.md
hamedandClaude Opus 4.8 e78b0c4b4c chore(prompt): split SEO audit follow-up into backend task prompts
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>
2026-07-19 08:09:00 +03:30

6.6 KiB
Raw Permalink Blame History

افزودن شهر به لیست پزشکان + رفع سقف خاموش 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.jsextractEntityCityId). این پرامپت آن رفتار را تغییر نمی‌دهد.

وظایف

۱. افزودن شهر به 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 لازم نیست.