From 5caaba1f7b5d782d5b65429980360af79cce1c6b Mon Sep 17 00:00:00 2001 From: hwnagjokim Date: Wed, 17 Jun 2026 11:43:45 +0900 Subject: [PATCH] feat(team): add github weekly summary detail api --- openapi/openapi.yaml | 32 ++++++++++++ .../team/hooks/use-team-space-queries.ts | 51 +++++++++++++++++++ src/lib/api/mocks/handlers.ts | 48 +++++++++++++++++ src/lib/api/team-space.contract.ts | 7 +++ src/lib/api/team-space.ts | 12 +++++ src/lib/types/team-space.ts | 5 ++ 6 files changed, 155 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d34aa1c..97cd2ad 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1142,6 +1142,31 @@ paths: $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' + /team-space/{projectGroupId}/github/users/{targetUserId}/weekly-summaries/{weeklyGithubSummaryId}: + get: + tags: [TeamSpace] + security: + - bearerAuth: [] + summary: Get one weekly GitHub activity summary for a team member + parameters: + - $ref: '#/components/parameters/ProjectGroupId' + - $ref: '#/components/parameters/TargetUserId' + - $ref: '#/components/parameters/WeeklyGithubSummaryId' + responses: + '200': + description: Weekly GitHub summary + content: + application/json: + schema: + $ref: '#/components/schemas/GithubWeeklySummary' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' components: securitySchemes: bearerAuth: @@ -1184,6 +1209,13 @@ components: schema: type: integer format: int64 + WeeklyGithubSummaryId: + in: path + name: weeklyGithubSummaryId + required: true + schema: + type: integer + format: int64 schemas: ApiError: type: object diff --git a/src/features/team/hooks/use-team-space-queries.ts b/src/features/team/hooks/use-team-space-queries.ts index af6025f..b3576e0 100644 --- a/src/features/team/hooks/use-team-space-queries.ts +++ b/src/features/team/hooks/use-team-space-queries.ts @@ -12,6 +12,7 @@ import { getGithubInstallationStatus, getGithubRepositories, getGithubRepositoryContributions, + getGithubWeeklySummary, getGithubWeeklySummaries, getTeamRule, regenerateDevGuide, @@ -24,6 +25,7 @@ import type { DevGuideQueryResponse, GithubAppInstallationCompleteRequest, GithubRepositoryContributionRequest, + GithubWeeklySummaryDetailRequest, RegenerateDevGuideRequest, SetGithubRepositoriesRequest, TeamRuleResponse, @@ -64,6 +66,20 @@ export const teamSpaceQueryKeys = { targetUserId, "weekly-summaries", ] as const, + githubWeeklySummary: ( + projectGroupId: number, + targetUserId: number, + weeklyGithubSummaryId: number, + ) => + [ + "team-space", + projectGroupId, + "github", + "users", + targetUserId, + "weekly-summaries", + weeklyGithubSummaryId, + ] as const, githubStatus: (projectGroupId: number) => ["team-space", projectGroupId, "github", "status"] as const, teamRule: (projectGroupId: number) => @@ -420,3 +436,38 @@ export function useGithubWeeklySummariesQuery( staleTime: githubContributionStaleTimeMs, }); } + +export function useGithubWeeklySummaryQuery( + projectGroupId: number | undefined, + targetUserId: number | undefined, + weeklyGithubSummaryId: number | undefined, + enabled = true, +) { + const hasIds = + typeof projectGroupId === "number" && + typeof targetUserId === "number" && + typeof weeklyGithubSummaryId === "number"; + + return useQuery({ + enabled: enabled && hasIds, + queryFn: () => + getGithubWeeklySummary({ + projectGroupId: requireProjectGroupId(projectGroupId), + targetUserId: requireNumericId(targetUserId, "Target user id"), + weeklyGithubSummaryId: requireNumericId( + weeklyGithubSummaryId, + "Weekly Github summary id", + ), + } satisfies GithubWeeklySummaryDetailRequest), + queryKey: hasIds + ? teamSpaceQueryKeys.githubWeeklySummary( + projectGroupId, + targetUserId, + weeklyGithubSummaryId, + ) + : teamSpaceQueryKeys.disabled, + refetchOnWindowFocus: false, + retry: false, + staleTime: githubContributionStaleTimeMs, + }); +} diff --git a/src/lib/api/mocks/handlers.ts b/src/lib/api/mocks/handlers.ts index 426eb3c..d4e1482 100644 --- a/src/lib/api/mocks/handlers.ts +++ b/src/lib/api/mocks/handlers.ts @@ -3001,4 +3001,52 @@ export const handlers = [ return HttpResponse.json(createMockGithubWeeklySummaries(targetUserId)); }, ), + + http.get( + getPath( + "/team-space/:projectGroupId/github/users/:targetUserId/weekly-summaries/:weeklyGithubSummaryId", + ), + async ({ params, request }) => { + await delay(300); + syncSessionFromRequest(request); + + const projectGroupId = Number(params.projectGroupId); + const targetUserId = Number(params.targetUserId); + const weeklyGithubSummaryId = Number(params.weeklyGithubSummaryId); + const accessError = assertProjectGroupAccess(projectGroupId); + + if (accessError) { + return accessError; + } + + const targetMember = activeProjectGroup?.members.find( + (member) => member.userId === targetUserId, + ); + + if (!targetMember) { + return buildErrorResponse( + 404, + "팀 멤버를 찾을 수 없습니다.", + "PROJECT_GROUP_MEMBER_NOT_FOUND", + ); + } + + const summary = createMockGithubWeeklySummaries( + targetUserId, + ).summaries.find( + (candidate) => + candidate.weeklyGithubSummaryId === weeklyGithubSummaryId, + ); + + if (!summary) { + return buildErrorResponse( + 404, + "주간 GitHub 요약을 찾을 수 없습니다.", + "WEEKLY_GITHUB_SUMMARY_NOT_FOUND", + ); + } + + return HttpResponse.json(summary); + }, + ), ]; diff --git a/src/lib/api/team-space.contract.ts b/src/lib/api/team-space.contract.ts index 4b64f4b..aaa3393 100644 --- a/src/lib/api/team-space.contract.ts +++ b/src/lib/api/team-space.contract.ts @@ -2,12 +2,15 @@ import { confirmDevGuide, getDevGuideHistories, getDevGuideHistoryContent, + getGithubWeeklySummary, getGithubWeeklySummaries, } from "@/lib/api/team-space"; import type { ConfirmDevGuideRequest, DevGuideHistoryContentResponse, DevGuideHistoryListResponse, + GithubWeeklySummary, + GithubWeeklySummaryDetailRequest, GithubWeeklySummaryListResponse, GithubWeeklySummaryRequest, } from "@/lib/types/team-space"; @@ -23,11 +26,15 @@ type TeamSpaceServerOnlyApiContract = { getGithubWeeklySummaries: ( payload: GithubWeeklySummaryRequest, ) => Promise; + getGithubWeeklySummary: ( + payload: GithubWeeklySummaryDetailRequest, + ) => Promise; }; export const teamSpaceServerOnlyApiContract = { confirmDevGuide, getDevGuideHistories, getDevGuideHistoryContent, + getGithubWeeklySummary, getGithubWeeklySummaries, } satisfies TeamSpaceServerOnlyApiContract; diff --git a/src/lib/api/team-space.ts b/src/lib/api/team-space.ts index b4d91e1..25d0bc5 100644 --- a/src/lib/api/team-space.ts +++ b/src/lib/api/team-space.ts @@ -10,6 +10,8 @@ import type { GithubRepositoryContributionRequest, GithubRepositoryContributionResponse, GithubRepositoryListResponse, + GithubWeeklySummary, + GithubWeeklySummaryDetailRequest, GithubWeeklySummaryListResponse, GithubWeeklySummaryRequest, RegenerateDevGuideRequest, @@ -177,3 +179,13 @@ export function getGithubWeeklySummaries({ `/team-space/${projectGroupId}/github/users/${targetUserId}/weekly-summaries`, ); } + +export function getGithubWeeklySummary({ + projectGroupId, + targetUserId, + weeklyGithubSummaryId, +}: GithubWeeklySummaryDetailRequest) { + return apiRequest( + `/team-space/${projectGroupId}/github/users/${targetUserId}/weekly-summaries/${weeklyGithubSummaryId}`, + ); +} diff --git a/src/lib/types/team-space.ts b/src/lib/types/team-space.ts index b6086e4..ea0ae25 100644 --- a/src/lib/types/team-space.ts +++ b/src/lib/types/team-space.ts @@ -152,6 +152,11 @@ export interface GithubWeeklySummaryRequest { targetUserId: number; } +export interface GithubWeeklySummaryDetailRequest + extends GithubWeeklySummaryRequest { + weeklyGithubSummaryId: number; +} + export interface GithubWeeklySummaryContent { followUpSuggestions: string[]; issueHighlights: string[];