feat(blog): enhance metadata generation and blog fetching by city context
This commit is contained in:
+11
-2
@@ -31,12 +31,21 @@ export async function generateMetadata({ params }) {
|
||||
if (!blog) notFound();
|
||||
|
||||
try {
|
||||
// برند از شهرِ خودِ پست میآید، نه از دامنهای که اتفاقاً آن را سرو میکند —
|
||||
// وگرنه پست یاسوجی که روی دامنهٔ یزد باز شود عنوانش «یزد نوبت» میشد در حالی
|
||||
// که canonical آن به یاسوج اشاره میکند. پست سراسری برند دامنهٔ جاری را میگیرد.
|
||||
const blogCity = findCityById(extractEntityCityId(blog));
|
||||
const brand = blogCity?.site_name || siteName;
|
||||
|
||||
const title = `${blog.title} | ${siteName}`;
|
||||
const title = `${blog.title} | ${brand}`;
|
||||
const rawText = (blog.summary || blog.body || "")
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.trim();
|
||||
const description = rawText.slice(0, 160) || blog.title;
|
||||
const baseDescription = rawText.slice(0, 160) || blog.title;
|
||||
// نام شهر فقط برای پست شهریافته؛ پست سراسری هیچ شهری نمیگیرد.
|
||||
const description = blogCity
|
||||
? `${baseDescription}`.slice(0, 150) + ` | ${blogCity.name}`
|
||||
: baseDescription;
|
||||
const image = blog.image_url ? imageUrl(blog.image_url) : FALLBACK_IMG;
|
||||
|
||||
// C1-b — پست شهریافته به دامنهٔ همان شهر canonical میشود؛
|
||||
|
||||
+6
-2
@@ -18,10 +18,14 @@ export async function generateMetadata() {
|
||||
};
|
||||
}
|
||||
|
||||
export default function Blogs() {
|
||||
export default async function Blogs() {
|
||||
const { matchedCity, isRoot } = await getStateInfo();
|
||||
|
||||
// روی دامنهٔ شهری: پستهای همان شهر + سراسری. روی دامنهٔ ریشه: همهٔ پستها.
|
||||
// تشخیص شهر سمت سرور انجام میشود (همان الگوی app/specialties/page.js).
|
||||
return (
|
||||
<Layout name="/blogs">
|
||||
<BlogsPage />
|
||||
<BlogsPage cityId={isRoot ? null : (matchedCity?.id ?? null)} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
+9
-6
@@ -175,14 +175,18 @@ function getSpecialtyUrls(baseUrl) {
|
||||
}
|
||||
|
||||
// هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر،
|
||||
// پست سراسری (بدون شهر) → دامنهٔ اصلی.
|
||||
async function getBlogUrls(baseUrl, scope, currentDomain) {
|
||||
// پست سراسری (یا شهرِ بدون دامنهٔ اختصاصی) → دامنهٔ اصلی.
|
||||
//
|
||||
// تطبیق با شناسهٔ شهرِ scope انجام میشود نه با رشتهٔ دامنه: scope خودش از subdomain
|
||||
// استخراج شده، پس با پورت و TLD متفاوت (محیط محلی) هم درست کار میکند.
|
||||
async function getBlogUrls(baseUrl, scope) {
|
||||
const blogs = await fetchAllPages('/api/v1/blogs');
|
||||
return blogs
|
||||
.filter((b) => b?.slug || b?.uuid)
|
||||
.filter((b) => {
|
||||
const cityDomain = findDomainByCityId(extractEntityCityId(b));
|
||||
return cityDomain ? cityDomain === currentDomain : scope.isRoot;
|
||||
const cityId = extractEntityCityId(b);
|
||||
if (!findDomainByCityId(cityId)) return scope.isRoot;
|
||||
return !scope.isRoot && Number(scope.matchedCity?.id) === Number(cityId);
|
||||
})
|
||||
.map((b) =>
|
||||
withLastModified(
|
||||
@@ -201,12 +205,11 @@ export default async function sitemap() {
|
||||
const domain = await getCurrentDomain();
|
||||
const baseUrl = getBaseUrl(domain);
|
||||
const scope = getCityScope(domain);
|
||||
const normalizedDomain = domain.toLowerCase().replace(/^www\./, '').split(':')[0];
|
||||
|
||||
const [doctorUrls, clinicUrls, blogUrls] = await Promise.all([
|
||||
getDoctorUrls(baseUrl, scope),
|
||||
getClinicUrls(baseUrl, scope),
|
||||
getBlogUrls(baseUrl, scope, normalizedDomain),
|
||||
getBlogUrls(baseUrl, scope),
|
||||
]);
|
||||
|
||||
const allUrls = [
|
||||
|
||||
@@ -7,7 +7,7 @@ import LatestArticles from "./latestArticles";
|
||||
import { request } from "@/services/response";
|
||||
import { normalizeBlog } from "@/helper";
|
||||
|
||||
function BlogsPage() {
|
||||
function BlogsPage({ cityId = null }) {
|
||||
const [blogs, setBlogs] = useState([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
@@ -25,6 +25,10 @@ function BlogsPage() {
|
||||
if (selectedTag) {
|
||||
params.tag = selectedTag;
|
||||
}
|
||||
// backend با city_id پستهای آن شهر «و» پستهای سراسری را برمیگرداند
|
||||
if (cityId) {
|
||||
params.city_id = cityId;
|
||||
}
|
||||
const response = await request.getBlogs(params);
|
||||
setBlogs((response?.data || []).map(normalizeBlog));
|
||||
setTotalPages(response?.meta?.totalPages || 1);
|
||||
@@ -37,7 +41,7 @@ function BlogsPage() {
|
||||
};
|
||||
|
||||
fetchBlogs();
|
||||
}, [page, selectedTag]);
|
||||
}, [page, selectedTag, cityId]);
|
||||
|
||||
const handlePageChange = (event, value) => {
|
||||
setPage(value);
|
||||
|
||||
@@ -75,3 +75,17 @@ describe("extractEntityCityId", () => {
|
||||
expect(extractEntityCityId(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("شکل پاسخ بلاگ (city آبجکت یا null)", () => {
|
||||
it("پست شهریافته → شناسه و دامنهٔ همان شهر", () => {
|
||||
const post = { slug: "x", city: { id: "123", name: "یاسوج" } };
|
||||
expect(extractEntityCityId(post)).toBe("123");
|
||||
expect(findDomainByCityId(extractEntityCityId(post))).toBe("yasuj-nobat.ir");
|
||||
});
|
||||
|
||||
it("پست سراسری (city: null) → بدون شهر، بدون دامنه", () => {
|
||||
const post = { slug: "x", city: null };
|
||||
expect(extractEntityCityId(post)).toBeNull();
|
||||
expect(findDomainByCityId(extractEntityCityId(post))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user