diff --git a/fundamentals/today-i-learned/src/api/graphql/discussions.ts b/fundamentals/today-i-learned/src/api/graphql/discussions.ts index 0759ed0e..b81396c6 100644 --- a/fundamentals/today-i-learned/src/api/graphql/discussions.ts +++ b/fundamentals/today-i-learned/src/api/graphql/discussions.ts @@ -1,12 +1,13 @@ // GitHub Discussions 관련 GraphQL 쿼리 정의 export const GET_DISCUSSIONS_QUERY = ` - query GetDiscussions($owner: String!, $repo: String!, $first: Int!, $after: String) { + query GetDiscussions($owner: String!, $repo: String!, $first: Int!, $after: String, $categoryId: ID) { repository(owner: $owner, name: $repo) { discussions( first: $first after: $after orderBy: { field: CREATED_AT, direction: DESC } + categoryId: $categoryId ) { pageInfo { hasNextPage @@ -146,12 +147,13 @@ export const GET_REPOSITORY_INFO_QUERY = ` `; export const GET_INFINITE_DISCUSSIONS_QUERY = ` - query GetInfiniteDiscussions($owner: String!, $repo: String!, $first: Int!, $after: String, $orderBy: DiscussionOrder) { + query GetInfiniteDiscussions($owner: String!, $repo: String!, $first: Int!, $after: String, $orderBy: DiscussionOrder, $categoryId: ID) { repository(owner: $owner, name: $repo) { discussions( first: $first after: $after orderBy: $orderBy + categoryId: $categoryId ) { pageInfo { hasNextPage diff --git a/fundamentals/today-i-learned/src/api/hooks/useDiscussions.ts b/fundamentals/today-i-learned/src/api/hooks/useDiscussions.ts index 73f5581e..e2a544f8 100644 --- a/fundamentals/today-i-learned/src/api/hooks/useDiscussions.ts +++ b/fundamentals/today-i-learned/src/api/hooks/useDiscussions.ts @@ -23,6 +23,7 @@ import { removeDiscussionReaction, type DiscussionsApiParams } from "@/api/remote/discussions"; +import { CATEGORY_ID } from "@/constants"; // NOTE: 만약 쿼리 옵션으로 분리된다면 각각의 쿼리 옵션에서 인라인하기 (중앙 집권형 쿼리 키 x) export const DISCUSSIONS_QUERY_KEYS = { @@ -43,14 +44,14 @@ export const DISCUSSIONS_QUERY_KEYS = { interface UseDiscussionsParams { owner?: string; repo?: string; - categoryName?: string; + categoryId?: string; enabled?: boolean; } interface UseInfiniteDiscussionsParams { owner?: string; repo?: string; - categoryName?: string; + categoryId?: string; pageSize?: number; sortBy?: "latest" | "lastActivity" | "created" | "popularity"; filterBy?: { @@ -62,18 +63,18 @@ interface UseInfiniteDiscussionsParams { export function useAllDiscussionsWithFullData({ owner = ENV_CONFIG.GITHUB_OWNER, repo = ENV_CONFIG.GITHUB_REPO, - categoryName = "Today I Learned", + categoryId = CATEGORY_ID.TODAY_I_LEARNED, enabled = true }: UseDiscussionsParams = {}) { const { user } = useAuth(); return useQuery({ - queryKey: DISCUSSIONS_QUERY_KEYS.list({ owner, repo, categoryName }), + queryKey: DISCUSSIONS_QUERY_KEYS.list({ owner, repo, categoryId }), queryFn: () => fetchAllDiscussions({ owner, repo, - categoryName, + categoryId, accessToken: user?.accessToken }), enabled, @@ -86,7 +87,7 @@ export function useAllDiscussionsWithFullData({ export function useInfiniteDiscussions({ owner = ENV_CONFIG.GITHUB_OWNER, repo = ENV_CONFIG.GITHUB_REPO, - categoryName, + categoryId = CATEGORY_ID.TODAY_I_LEARNED, pageSize = 5, sortBy = "latest", filterBy, @@ -98,7 +99,7 @@ export function useInfiniteDiscussions({ queryKey: DISCUSSIONS_QUERY_KEYS.infinite({ owner, repo, - categoryName, + categoryId, sortBy, filterBy }), @@ -106,6 +107,7 @@ export function useInfiniteDiscussions({ fetchInfiniteDiscussions({ owner, repo, + categoryId, first: pageSize, after: pageParam, sortBy, @@ -125,7 +127,7 @@ export function useWeeklyTopDiscussions({ owner = ENV_CONFIG.GITHUB_OWNER, repo = ENV_CONFIG.GITHUB_REPO, limit -}: Omit & { limit?: number } = {}) { +}: Omit & { limit?: number } = {}) { const { user } = useAuth(); return useSuspenseQuery({ @@ -148,7 +150,7 @@ export function useRepositoryInfo({ owner = ENV_CONFIG.GITHUB_OWNER, repo = ENV_CONFIG.GITHUB_REPO, enabled = true -}: Omit = {}) { +}: Omit = {}) { const { user } = useAuth(); return useQuery({ @@ -217,7 +219,7 @@ export function useMyContributions({ owner = ENV_CONFIG.GITHUB_OWNER, repo = ENV_CONFIG.GITHUB_REPO, enabled = true -}: Omit = {}) { +}: Omit = {}) { const { user } = useAuth(); return useQuery({ diff --git a/fundamentals/today-i-learned/src/api/remote/discussions.ts b/fundamentals/today-i-learned/src/api/remote/discussions.ts index d60754f4..4a222321 100644 --- a/fundamentals/today-i-learned/src/api/remote/discussions.ts +++ b/fundamentals/today-i-learned/src/api/remote/discussions.ts @@ -1,5 +1,6 @@ import { graphqlRequest } from "@/api/client"; import { PAGE_SIZE } from "@/constants/github"; +import { CATEGORY_ID } from "@/constants"; import { GET_DISCUSSIONS_QUERY, CREATE_DISCUSSION_MUTATION, @@ -63,13 +64,14 @@ export interface CreatePostResponse extends GitHubDiscussion {} export interface DiscussionsApiParams { owner: string; repo: string; - categoryName?: string; + categoryId?: string; accessToken?: string; } export interface PaginatedDiscussionsParams extends DiscussionsApiParams { first?: number; after?: string | null; + categoryId?: string; } export interface CreateDiscussionParams { @@ -115,6 +117,7 @@ export async function fetchDiscussionsPage({ repo, first = PAGE_SIZE.DEFAULT, after, + categoryId, accessToken }: PaginatedDiscussionsParams): Promise<{ discussions: GitHubDiscussion[]; @@ -125,7 +128,7 @@ export async function fetchDiscussionsPage({ }> { const data = await graphqlRequest( GET_DISCUSSIONS_QUERY, - { owner, repo, first, after }, + { owner, repo, first, after, categoryId }, accessToken ); @@ -151,7 +154,6 @@ export async function fetchDiscussionsPage({ export async function fetchAllDiscussions({ owner, repo, - categoryName, accessToken }: DiscussionsApiParams): Promise { const allDiscussions: GitHubDiscussion[] = []; @@ -165,17 +167,11 @@ export async function fetchAllDiscussions({ owner, repo, after: cursor, + categoryId: CATEGORY_ID.TODAY_I_LEARNED, accessToken }); - // 클라이언트 사이드 카테고리 필터링 - const filteredDiscussions = categoryName - ? discussions.filter( - (discussion) => discussion.category?.name === categoryName - ) - : discussions; - - allDiscussions.push(...filteredDiscussions); + allDiscussions.push(...discussions); hasNextPage = pageInfo.hasNextPage; cursor = pageInfo.endCursor; @@ -194,7 +190,7 @@ export async function fetchWeeklyTopDiscussions({ owner, repo, accessToken -}: Omit): Promise { +}: Omit): Promise { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); @@ -244,7 +240,7 @@ export async function fetchRepositoryInfo({ owner, repo, accessToken -}: Omit): Promise<{ +}: Omit): Promise<{ repositoryId: string; categories: Array<{ id: string; name: string; description?: string }>; }> { @@ -270,13 +266,14 @@ export async function fetchRepositoryInfo({ export async function fetchInfiniteDiscussions({ owner, repo, + categoryId = CATEGORY_ID.TODAY_I_LEARNED, first = PAGE_SIZE.INFINITE_SCROLL, after, sortBy = "latest", filterBy, accessToken }: InfiniteDiscussionsParams): Promise { - // Search API를 사용해야 하는 경우: 라벨 필터링 또는 popularity 정렬 + // 라벨 필터링이나 popularity 정렬이 필요한 경우 Search API 사용 if (filterBy?.label || sortBy === "popularity") { const getSortQuery = (sort: string) => { switch (sort) { @@ -337,7 +334,8 @@ export async function fetchInfiniteDiscussions({ repo, first, after: after || null, - orderBy: getOrderBy(sortBy) + orderBy: getOrderBy(sortBy), + categoryId: CATEGORY_ID.TODAY_I_LEARNED }, accessToken ); @@ -368,7 +366,7 @@ export async function fetchMyContributions({ repo, accessToken, authorLogin -}: Omit & { +}: Omit & { authorLogin: string; }): Promise { const allContributions: ContributionData[] = []; diff --git a/fundamentals/today-i-learned/src/constants/index.ts b/fundamentals/today-i-learned/src/constants/index.ts index df33685e..82b53ae8 100644 --- a/fundamentals/today-i-learned/src/constants/index.ts +++ b/fundamentals/today-i-learned/src/constants/index.ts @@ -167,3 +167,19 @@ export const STORAGE_KEYS = { USER_PREFERENCES: "til-user-preferences", DRAFT_POST: "til-draft-post" } as const; + +// === github discussions Category IDs === +/** + * GitHub Discussion 카테고리 정보 + * + * 카테고리 ID는 변경되지 않는 고정값입니다. + * + * 새로운 카테고리 추가 방법: + * 1. 개발 서버 실행: yarn dev + * 2. http://localhost:5173/today-i-learned/dev-tools/category-id 접속 + * 3. 카테고리 목록에서 원하는 카테고리의 ID를 복사 + * 4. 아래 객체에 새로운 항목 추가 + */ +export const CATEGORY_ID = { + TODAY_I_LEARNED: "DIC_kwDONfHk5s4CuCWu" +} as const; diff --git a/fundamentals/today-i-learned/src/main.tsx b/fundamentals/today-i-learned/src/main.tsx index 28c62552..0ceae0f1 100644 --- a/fundamentals/today-i-learned/src/main.tsx +++ b/fundamentals/today-i-learned/src/main.tsx @@ -14,6 +14,8 @@ import { TimelinePage } from "./pages/timeline/TimelinePage"; import { QueryProvider } from "./providers/QueryProvider.tsx"; import { ToastProvider } from "./contexts/ToastContext"; import { RootLayout } from "./components/shared/layout/RootLayout.tsx"; +import { CategoryIdFinder } from "./pages/dev-tools/CategoryIdFinder"; +import { isDevelopment } from "./utils/env.ts"; const router = createBrowserRouter( [ @@ -36,7 +38,16 @@ const router = createBrowserRouter( { path: "post/:id", element: - } + }, + // 개발 환경에서만 표시 + ...(isDevelopment() + ? [ + { + path: "dev-tools/category-id", + element: + } + ] + : []) ] } ], diff --git a/fundamentals/today-i-learned/src/pages/dev-tools/CategoryIdFinder.tsx b/fundamentals/today-i-learned/src/pages/dev-tools/CategoryIdFinder.tsx new file mode 100644 index 00000000..a41a3871 --- /dev/null +++ b/fundamentals/today-i-learned/src/pages/dev-tools/CategoryIdFinder.tsx @@ -0,0 +1,182 @@ +/** + * 개발자 도구: 카테고리 ID 찾기 + * + * 사용법: + * 1. 개발 서버 실행: yarn dev + * 2. 브라우저에서 /dev-tools/category-id 접속 + * 3. 카테고리 이름 입력 또는 전체 목록 확인 + * 4. categoryId를 복사해서 사용 + * + * 주의: 이 페이지는 개발 전용이며, production 빌드에서는 제외되어야 합니다. + */ + +import { useState } from "react"; +import { useRepositoryInfo } from "@/api/hooks/useDiscussions"; +import { ENV_CONFIG } from "@/utils/env"; + +export function CategoryIdFinder() { + const [searchTerm, setSearchTerm] = useState(""); + const { data: repoInfo, isLoading, error } = useRepositoryInfo(); + + const filteredCategories = repoInfo?.categories.filter((cat) => + cat.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + alert(`Copied: ${text}`); + }; + + return ( +
+

🔍 GitHub Discussion Category ID Finder

+

+ Repository: {ENV_CONFIG.GITHUB_OWNER}/{ENV_CONFIG.GITHUB_REPO} +

+ + {isLoading &&

Loading categories...

} + {error && ( +
+ Error: {error.message} +
+ )} + + {repoInfo && ( + <> +
+ setSearchTerm(e.target.value)} + style={{ + width: "100%", + padding: "0.75rem", + fontSize: "1rem", + border: "2px solid #ddd", + borderRadius: "8px" + }} + /> +
+ +
+

Categories ({filteredCategories?.length || 0})

+ {filteredCategories?.map((category) => ( +
+
+
+

{category.name}

+ {category.description && ( +

+ {category.description} +

+ )} +
+ +
+
+ {category.id} +
+
+ ))} +
+ +
+

💡 사용 방법

+
    +
  1. 위에서 원하는 카테고리를 찾습니다
  2. +
  3. "Copy ID" 버튼을 클릭해서 ID를 복사합니다
  4. +
  5. 코드에서 해당 ID를 사용합니다
  6. +
+
+ +
+

📝 코드 예시

+
+              {`// src/constants/categories.ts
+export const DISCUSSION_CATEGORIES = {
+  TODAY_I_LEARNED: {
+    name: "Today I Learned",
+    id: "${filteredCategories?.[0]?.id || "CATEGORY_ID_HERE"}"
+  }
+} as const;`}
+            
+
+ + )} +
+ ); +} diff --git a/fundamentals/today-i-learned/src/pages/profile/hooks/useUserActivity.ts b/fundamentals/today-i-learned/src/pages/profile/hooks/useUserActivity.ts index 3208b9e6..cabb5ca9 100644 --- a/fundamentals/today-i-learned/src/pages/profile/hooks/useUserActivity.ts +++ b/fundamentals/today-i-learned/src/pages/profile/hooks/useUserActivity.ts @@ -4,6 +4,7 @@ import { useInfiniteDiscussions } from "@/api/hooks/useDiscussions"; import { useIntersectionObserver } from "@/hooks/useIntersectionObserver"; import { processUserPosts } from "@/utils/postFilters"; import { PAGE_SIZE } from "@/constants/github"; +import { CATEGORY_ID } from "@/constants"; type SortFilter = "created" | "lastActivity"; @@ -20,7 +21,7 @@ export function useUserActivity() { error, refetch } = useInfiniteDiscussions({ - categoryName: "Today I Learned", + categoryId: CATEGORY_ID.TODAY_I_LEARNED, pageSize: PAGE_SIZE.DEFAULT }); diff --git a/fundamentals/today-i-learned/src/pages/profile/hooks/useUserHallOfFame.ts b/fundamentals/today-i-learned/src/pages/profile/hooks/useUserHallOfFame.ts index 68aee68a..735970a5 100644 --- a/fundamentals/today-i-learned/src/pages/profile/hooks/useUserHallOfFame.ts +++ b/fundamentals/today-i-learned/src/pages/profile/hooks/useUserHallOfFame.ts @@ -3,6 +3,7 @@ import { useUserProfile } from "@/api/hooks/useUser"; import { useInfiniteDiscussions } from "@/api/hooks/useDiscussions"; import { filterUserPosts } from "@/utils/postFilters"; import { PAGE_SIZE } from "@/constants/github"; +import { CATEGORY_ID } from "@/constants"; const INITIAL_DISPLAY_COUNT = 6; @@ -19,7 +20,7 @@ export function useUserHallOfFame() { error, refetch } = useInfiniteDiscussions({ - categoryName: "Today I Learned", + categoryId: CATEGORY_ID.TODAY_I_LEARNED, filterBy: { label: "성지 ⛲" }, pageSize: PAGE_SIZE.DEFAULT }); diff --git a/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx index 02785531..02480ade 100644 --- a/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx +++ b/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx @@ -9,6 +9,7 @@ import { useUserProfile } from "@/api/hooks/useUser"; import { css } from "@styled-system/css"; import { SortOption } from "../types"; import { useSearchParams } from "react-router-dom"; +import { CATEGORY_ID } from "@/constants"; export function PostList() { const [searchParams] = useSearchParams({ sort: "newest" }); @@ -99,23 +100,23 @@ const getPostListProps = (sortOption: SortOption) => { switch (sortOption) { case "newest": return { - categoryName: "Today I Learned", + categoryId: CATEGORY_ID.TODAY_I_LEARNED, sortBy: "latest" as const }; case "realtime": return { - categoryName: "Today I Learned", + categoryId: CATEGORY_ID.TODAY_I_LEARNED, sortBy: "lastActivity" as const }; case "hall-of-fame": return { - categoryName: "Today I Learned", + categoryId: CATEGORY_ID.TODAY_I_LEARNED, sortBy: "latest" as const, filterBy: { label: "성지 ⛲" } }; default: return { - categoryName: "Today I Learned", + categoryId: CATEGORY_ID.TODAY_I_LEARNED, sortBy: "latest" as const }; }