diff --git a/app/doctor/[slug]/page.js b/app/doctor/[slug]/page.js index f7019e0..bbb1d5f 100644 --- a/app/doctor/[slug]/page.js +++ b/app/doctor/[slug]/page.js @@ -6,7 +6,7 @@ import { getStateInfo } from "@/lib/getStateInfo"; import { getEntityOrigin } from "@/lib/getCanonicalUrl"; import { extractEntityCityId, findCityById } from "@/lib/domainHelpers"; import { buildDoctorFaq } from "@/lib/specialtyContent"; -import { isThinDoctor } from "@/lib/entityQuality"; +import { isNoindexDoctor } from "@/lib/entityQuality"; import specialtiesData from "@/data/specialties.json"; import { safeJsonLd } from "@/lib/sanitize"; import { imageUrl, doctorTitle } from "@/helper"; @@ -96,7 +96,7 @@ export async function generateMetadata({ params }) { title, description, alternates: { canonical: `${origin}/doctor/${doctor.uuid}` }, - ...(isThinDoctor(doctor) && { robots: { index: false, follow: true } }), + ...(isNoindexDoctor(doctor) && { robots: { index: false, follow: true } }), openGraph: { title, description, diff --git a/app/sitemap.js b/app/sitemap.js index 83fe327..4111a22 100644 --- a/app/sitemap.js +++ b/app/sitemap.js @@ -4,7 +4,7 @@ import citiesData from '@/data/city.json'; import statesData from '@/data/state.json'; import { isRootCity } from '@/lib/rootCity'; import { extractEntityCityId, findDomainByCityId } from '@/lib/domainHelpers'; -import { isThinClinic, isThinDoctor } from '@/lib/entityQuality'; +import { isThinClinic, isNoindexDoctor } from '@/lib/entityQuality'; import specialtiesData from '@/data/specialties.json'; import { isIndexableSpecialty } from '@/lib/specialtyIndexing'; @@ -119,7 +119,8 @@ function cityFilterParams(scope) { async function getPublishedDoctors(scope) { const doctors = await fetchAllPages('/api/v1/doctors', cityFilterParams(scope)); return doctors - .filter((d) => !isThinDoctor(d)) + // پروفایل بدون مالک noindex است، پس در sitemap هم اعلام نمی‌شود. + .filter((d) => !isNoindexDoctor(d)) // روی دامنهٔ اصلی فقط پزشکانی که شهرشان دامنهٔ اختصاصی ندارد؛ روی دامنهٔ شهری // فیلتر city_id سمت API قبلاً کار را کرده است. .filter((d) => !scope.isRoot || !findDomainByCityId(extractEntityCityId(d))); diff --git a/lib/entityQuality.js b/lib/entityQuality.js index e9f790a..4c4817f 100644 --- a/lib/entityQuality.js +++ b/lib/entityQuality.js @@ -14,8 +14,7 @@ function hasRealName(rawName) { } /** - * پزشک بی‌محتوا. معیار قبلی «owner_status !== claimed» بیش از حد سخت‌گیر بود و - * عملاً کل مارکت‌پلیس را از ایندکس بیرون می‌گذاشت — حالا فقط نبودِ محتوا مهم است. + * پزشک بی‌محتوا — فقط سنجهٔ محتوا، مستقل از مالکیت پروفایل. */ export function isThinDoctor(doctor) { if (!doctor?.uuid || !hasRealName(doctor.name)) return true; @@ -24,6 +23,23 @@ export function isThinDoctor(doctor) { return !hasSpecialty && !hasLocation; } +/** + * پروفایل بدون مالک (claim نشده). داده‌اش از منبع بیرونی آمده و صاحبش تأییدش نکرده، + * پس محتوای قابل اتکا برای ایندکس نیست. هم پاسخ لیست و هم پاسخ جزئیات `owner_status` + * دارند؛ نبودِ فیلد هم «مالک ندارد» تفسیر می‌شود تا هیچ‌وقت به‌طور خاموش ایندکس نشود. + */ +export function isUnclaimedDoctor(doctor) { + return doctor?.owner_status !== "claimed"; +} + +/** + * منبع واحدِ ایندکس‌پذیری پزشک: هم صفحهٔ پزشک (robots) و هم sitemap از این استفاده + * می‌کنند تا واگرا نشوند — چیزی که noindex است نباید در sitemap باشد. + */ +export function isNoindexDoctor(doctor) { + return isThinDoctor(doctor) || isUnclaimedDoctor(doctor); +} + /** * کلینیک بی‌محتوا. is_active:false = «موقتاً غیرفعال» تفسیر می‌شود (noindex، نه 410)، * چون رکورد و نوبت‌های تاریخی‌اش باقی می‌مانند و ممکن است دوباره فعال شود.