Files
nobat724_front/lib/domainHelpers.js
T

89 lines
4.3 KiB
JavaScript

// هلپرهای مشترک نگاشت «شهر ↔ دامنه» و نام نمایشی شهر.
// این ماژول pure است (بدون next/headers) تا در client، server، sitemap و تست قابل import باشد.
import citiesData from "@/data/city.json";
import { isRootCity } from "@/lib/rootCity";
const ROOT_DISPLAY_NAME = "ایران";
/**
* دامنهٔ شهریِ متناظر با یک city_id.
* رکورد ریشه (id:600 → nobat724.com) هرگز به‌عنوان «شهر» برنمی‌گردد.
* @returns {string|null} مثلا "yasuj-nobat.ir" یا null
*/
export function findDomainByCityId(cityId) {
if (cityId == null || cityId === "") return null;
const id = Number(cityId);
if (!Number.isFinite(id)) return null;
const city = citiesData.find((c) => Number(c.id) === id);
if (!city || isRootCity(city) || !city.domain) return null;
return city.domain;
}
/** رکورد شهر بر اساس id — رکورد ریشه شهر نیست. */
export function findCityById(cityId) {
if (cityId == null || cityId === "") return null;
const city = citiesData.find((c) => Number(c.id) === Number(cityId));
return city && !isRootCity(city) ? city : null;
}
/**
* شناسهٔ شهری که باید به‌عنوان scope دامنه به API فرستاده شود.
* دامنهٔ ریشه (nobat724) و دامنهٔ ناشناخته scope ندارند → همهٔ محتوا را می‌بینند؛
* دامنهٔ شهری فقط پست‌های خودش + سراسری‌ها را (فیلتر سمت بک‌اند).
* @returns {number|null}
*/
export function domainScopeCityId({ matchedCity, isRoot } = {}) {
if (isRoot) return null;
const id = Number(matchedCity?.id);
return Number.isFinite(id) ? id : null;
}
/**
* آیا این پست روی این دامنه وجود دارد؟ آینهٔ فیلتر بک‌اند
* (`b.city = :cityId OR b.city IS NULL`) برای استفاده در تست و رندر سمت کلاینت.
* پست بدون شهر سراسری است و همه‌جا دیده می‌شود؛ پست شهریافته (از جمله پستِ رکورد
* ریشهٔ nobat724) فقط روی دامنهٔ خودش.
*/
export function isBlogVisibleOnDomain(blog, scope = {}) {
const scopeCityId = domainScopeCityId(scope);
if (scopeCityId === null) return true; // دامنهٔ ریشه/ناشناخته: بدون محدودیت
const ownerId = blog?.city?.id ?? blog?.city_id ?? null;
if (ownerId == null) return true; // سراسری
return Number(ownerId) === scopeCityId;
}
/**
* نام قابل‌نمایش شهر برای H1/Title.
* روی دامنهٔ ریشه نام رکورد («نوبت 724») یک برند است نه شهر — به‌جای آن «ایران».
*/
export function resolveCityDisplayName(cityInfo) {
if (!cityInfo || isRootCity(cityInfo)) return ROOT_DISPLAY_NAME;
return cityInfo.name || ROOT_DISPLAY_NAME;
}
/**
* city_id یک موجودیت را از شکل‌های مختلف پاسخ API بیرون می‌کشد:
* - کلینیک: city: [{ id }]
* - پزشک: city خالی است و شهر واقعی داخل address[].city.id می‌نشیند
* - بلاگ: city_id مسطح
* اولین شهرِ دارای دامنهٔ اختصاصی برنده است (پزشک چند-شهری → یک شهر اصلی).
*/
export function extractEntityCityId(entity) {
if (!entity) return null;
// ترتیب مهم است: آدرس قبل از فیلد city سطح‌بالا.
// پاسخ جزئیات پزشک هر دو را دارد و می‌توانند اختلاف داشته باشند — city سطح‌بالا از
// یک رابطهٔ ManyToMany قدیمی می‌آید، ولی هم فیلتر city_id بک‌اند و هم فیلد city در
// پاسخ لیست بر پایهٔ آدرس‌اند. اگر اینجا فیلد قدیمی برنده شود، canonical پزشک به
// شهری می‌رود که sitemap او را آنجا نگذاشته است.
const candidates = [
entity.city_id,
...(Array.isArray(entity.address) ? entity.address.map((a) => a?.city?.id) : []),
...(Array.isArray(entity.city) ? entity.city.map((c) => c?.id) : [entity.city?.id]),
].filter((id) => id != null);
const withDomain = candidates.find((id) => findDomainByCityId(id));
return withDomain ?? candidates[0] ?? null;
}