From b6f9fcb4d44d9688c2f48e54d376e956ae3010c0 Mon Sep 17 00:00:00 2001 From: malloc72p Date: Wed, 1 Jul 2026 09:51:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(blog):=20=EC=B5=9C=EC=8B=A0=EA=B8=80=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=ED=94=BC=EB=93=9C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=ED=95=98=EB=8A=94=20=EC=84=A4=EC=A0=95=20=EB=A9=94?= =?UTF-8?q?=EC=BB=A4=EB=8B=88=EC=A6=98=20=EC=B6=94=EA=B0=80=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Constants.series.excludedFromLatestIds(빈 배열)를 find-posts가 존중하도록 구현 - 적용 범위: /posts/latest(seriesId='latest') + 랜딩 페이지 시리즈 루프만. 개별 시리즈 페이지·태그·사이트맵·전체 글 목록(prev/next 계산 포함)은 영향받지 않음 - 지금은 빈 배열이라 동작 변화 없음. 제외하고 싶은 시리즈가 생기면 ID만 추가하면 즉시 동작 - find-posts.test: 최신글 제외/개별 시리즈 미영향/전체 목록 미영향 3케이스 추가 Co-Authored-By: Claude Opus 4.8 --- apps/blog/src/app/(main)/page.tsx | 5 +++ apps/blog/src/libs/api/find-posts.test.ts | 39 +++++++++++++++++++++++ apps/blog/src/libs/api/find-posts.ts | 8 ++++- apps/blog/src/libs/constants.ts | 3 ++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/apps/blog/src/app/(main)/page.tsx b/apps/blog/src/app/(main)/page.tsx index d6466aa..2902133 100644 --- a/apps/blog/src/app/(main)/page.tsx +++ b/apps/blog/src/app/(main)/page.tsx @@ -20,6 +20,11 @@ export default async function LandingPage() { continue; } + // 랜딩 페이지(최신글 통합 피드)에서 제외할 시리즈. 시리즈 자체 페이지(/posts/{id})는 영향받지 않는다. + if (Constants.series.excludedFromLatestIds.includes(series.id)) { + continue; + } + const seriesPost = await findPosts({ seriesId: series.id, }); diff --git a/apps/blog/src/libs/api/find-posts.test.ts b/apps/blog/src/libs/api/find-posts.test.ts index 8dbd823..62088b2 100644 --- a/apps/blog/src/libs/api/find-posts.test.ts +++ b/apps/blog/src/libs/api/find-posts.test.ts @@ -1,5 +1,6 @@ import { findPosts } from '@libs/api/find-posts'; import { getAllMdxFiles, MdxFileInfo } from '@libs/api/mdx-utils'; +import { Constants } from '@libs/constants'; // fs를 읽는 getAllMdxFiles를 mock해 실제 글과 무관하게 "필터/정렬/limit" 로직만 검증한다. jest.mock('@libs/api/mdx-utils'); @@ -59,6 +60,44 @@ describe('findPosts', () => { expect(posts).toHaveLength(2); }); + describe('excludedFromLatestIds (#25)', () => { + const original = Constants.series.excludedFromLatestIds; + afterEach(() => { + Constants.series.excludedFromLatestIds = original; + }); + + it("seriesId가 'latest'면 제외 목록의 시리즈 글을 뺀다", async () => { + Constants.series.excludedFromLatestIds = ['fullstack']; + mockGetAll.mockResolvedValue([ + f('frontend/a', { series: 'frontend', date: '2026-01-01 00:00' }), + f('fullstack/x', { series: 'fullstack', date: '2026-02-01 00:00' }), + ]); + + const posts = await findPosts({ seriesId: 'latest' }); + expect(posts.map((p) => p.slug)).toEqual(['frontend/a']); + }); + + it('제외 목록에 있어도 해당 시리즈를 직접 조회하면 그대로 반환한다', async () => { + Constants.series.excludedFromLatestIds = ['fullstack']; + mockGetAll.mockResolvedValue([ + f('fullstack/x', { series: 'fullstack', date: '2026-02-01 00:00' }), + ]); + + const posts = await findPosts({ seriesId: 'fullstack' }); + expect(posts.map((p) => p.slug)).toEqual(['fullstack/x']); + }); + + it('seriesId 없이 호출하면(전체 글 목록) 제외 목록의 영향을 받지 않는다', async () => { + Constants.series.excludedFromLatestIds = ['fullstack']; + mockGetAll.mockResolvedValue([ + f('fullstack/x', { series: 'fullstack', date: '2026-02-01 00:00' }), + ]); + + const posts = await findPosts(); + expect(posts.map((p) => p.slug)).toEqual(['fullstack/x']); + }); + }); + it("orderBy 'createAtASC'는 오래된 순으로 정렬한다", async () => { mockGetAll.mockResolvedValue([ f('a', { series: 's', date: '2026-03-01 00:00' }), diff --git a/apps/blog/src/libs/api/find-posts.ts b/apps/blog/src/libs/api/find-posts.ts index 2c667fd..f38be1a 100644 --- a/apps/blog/src/libs/api/find-posts.ts +++ b/apps/blog/src/libs/api/find-posts.ts @@ -21,7 +21,13 @@ export async function findPosts( posts = posts.filter((post) => !post.frontMatter.isSeriesLanding); // 시리즈 필터링 - if (seriesId && seriesId !== Constants.series.latestId) { + if (seriesId === Constants.series.latestId) { + // '최신글' 통합 피드(/posts/latest)에서는 설정된 시리즈를 제외한다. + // seriesId가 없는 호출(전체 글 목록·태그·사이트맵 등)은 영향받지 않는다. + posts = posts.filter( + (post) => !Constants.series.excludedFromLatestIds.includes(post.frontMatter.series ?? ''), + ); + } else if (seriesId) { posts = posts.filter((post) => post.frontMatter.series === seriesId); } diff --git a/apps/blog/src/libs/constants.ts b/apps/blog/src/libs/constants.ts index af2c38f..57e7fcb 100644 --- a/apps/blog/src/libs/constants.ts +++ b/apps/blog/src/libs/constants.ts @@ -1,6 +1,9 @@ export const Constants = { series: { latestId: 'latest', + // '최신글' 통합 피드(랜딩 페이지 기본 보기 + /posts/latest)에서 제외할 시리즈 ID. + // 개별 시리즈 페이지(/posts/{id})와 태그·사이트맵 등 전체 글 목록에는 영향을 주지 않는다. + excludedFromLatestIds: [] as string[], }, a11y: { // skip 링크(본문 바로가기)와
타깃을 잇는 앵커 id. 두 파일이 같은 값을 공유하도록 단일화한다.